javascript的數據結構快速學-鏈表的實現

一邊學習前端,一邊經過博客的形式本身總結一些東西,固然也但願幫助一些和我同樣開始學前端的小夥伴。前端

若是出現錯誤,請在評論中指出,我也好本身糾正本身的錯誤node

author: thomaszhoubash

1-單項鍊表

鏈表定義:app

function LinkedList() {
		let Node = function(element) { // 輔助類,表示要添加到鏈表中的項
		  this.element = element;
		  this.next = null; //next屬性是隻想鏈表的下一個節點的指針
		};

		let length = 0,
			head = null;  // 頭指針
		this.append = function(element) {}; // 向鏈表尾部添加一個新的項
		this.insert = function(element, position) {}; // 向指定位置插入一個新的項
		this.removeAt = function(position) {}; // 移除鏈表中的指定項
		this.remove = function(element) {}; // 移除值爲element的項
		this.indexOf = function(element) {}; // 返回元素在鏈表中的索引,若是沒有則返回-1
		this.isEmpty = function() {}; // 判斷鏈表中是否爲空
		this.size = function() {}; // 鏈表的長度
		this.clear = function() {}; // 清空鏈表
		this.print = function() {}; //輸出鏈表的值
	}
複製代碼

append實現:向鏈表尾部添加一個新的項

this.append = function(element) { //向鏈表尾部添加一個新的項
			let node = new Node(element),
				       current;
			if (head === null) { // 鏈表中的第一個節點
                head = node;
			} else {
			    current = head;
			    while (current.next) { // 循環鏈表,直到找到最後一項
			      current = current.next;
				}
				current.next = node; // 找到最後一項,將next賦值爲node,創建鏈接
			}
			length += 1; //更新鏈表長度
		};
複製代碼

print實現:輸出鏈表的值

this.print = function() {
      let current = head;
		  for (let i = 0; i < length; i++) {
		    console.log(`第${i+1}個值:${current.element}`);
		    current = current.next;
			}
		};
複製代碼

insert實現:向指定位置插入一個新的項

向指定位置插入一個新的項,步驟以下:函數

  • 先進行position的越界判斷!!
  • case1: 當position=0的時候,那就是直接將當前head指向的節點變成新建node的next指向,head指向新建的node上
  • case2: 除case1外的狀況,先遍歷到以下圖所示的狀況,咱們設置兩個指針,每次遍歷都是current將值賦給previous,而後current向後推動,直到current指向position的位置,previous指向position的前一個
    image
  • 而後將新建的node,插入其中,而後改變其中的指針指向,具體看代碼
  • 最後將length加一
    image
this.insert = function(element, position) { // 向指定位置插入一個新的項
	  if (position >= 0 && position <= length) { // 檢查越界值
	    let node = new Node(element),
        current = head, // 設置兩指針
        previous;
        if (position === 0) { // 在第一個位置添加
          node.next = head;
          head = node;
		}else {
          for (let i = 0; i < position; i++) {
            // 使得current定在要插入的位置,previous定在要插入位置的前一個位置
            previous = current; 
            current = current.next;
          }
          // 將新項node插入進去
          node.next = current;
          previous.next = node;
		}
	  }else {
		return 0;
	  }
	  length += 1;
	};
複製代碼

對於函數參數含有position這類的位置參數,必定要注意越界判斷學習

removeAt實現:移除鏈表中的指定位置項

removeAt的思想和前面的insert方法很類似,惟一的不一樣就是移除指定項時,改變指針指向的代碼不一樣previous.next = current.next;測試

  • 具體思想如圖:將current指向的項(其實也就是position指向的項)移除,那就將圖中紅色叉的指針指向取消,而後用新的代替
  • 而後將lenght減一
    image
    只有previous.next = current.next;和insert方法不一樣,其餘思想和作法都同樣
this.removeaAt = function(position) { // 移除鏈表中的指定項
		      if (position >= 0 && position <= length) {
				let current = head,
					previous;
				if (position === 0) {
					head = head.next;
				}else {
				  for (let i = 0; i < position; i++) { //此處循環到current指向要移除的位置,同insert方法
				    previous = current;
				    current = current.next;
					}
					// 經過將previous的next變化到current.next,將指定項移除
				  previous.next = current.next;
				}
			  }else {
		        return 0;
			  }
			  length -= 1;
		    };
複製代碼

indexOf實現:返回元素在鏈表中的索引,若是沒有則返回-1

this.indexOf = function(element) { // 返回元素在鏈表中的索引,若是沒有則返回-1
		  let current = head; //
		  for (let i = 0; i < length; i++) {
			if (current.element === element) {
			  return i;
			}
			current = current.next;
		  }
		  return -1;
		};
複製代碼

remove實現:移除值爲element的項

this.remove = function(element) { // 移除值爲element的項
	// remove方法能夠直接經過複用 this.indexOf(element) 和 this.removeAt(position) 方法實現
	let index = this.indexOf(element);
	return this.removeaAt(index);
};
複製代碼

isEmpty、size、clear實現

this.isEmpty = function() { // 判斷鏈表中是否爲空
	return length === 0;
};

this.size = function() { // 鏈表的長度
	return length;
};

this.clear = function() { // 清空鏈表
	let current = head;
	for (let i = 0; i < length; i++) {
		this.removeAt(i);
	}
	length = 0;
	head = null;
};
複製代碼

測試函數

(function LinkedListTest() {
	  let linked = new LinkedList();
	  linked.append(2);
	  linked.append(3);
	  linked.append(4);
	  linked.print(); // 第1個值:2  第2個值:3  第3個值:4
	  
	  linked.insert(5, 1); // 位置從0開始
      linked.print(); // 第1個值:2   第2個值:5   第3個值:3   第4個值:4
	  linked.removeAt(1); // 至關於將上面插入的5再刪除
	  linked.print(); // 第1個值:2   第2個值:3   第3個值:4
	  
	  console.log(linked.indexOf(3)); // 1
      console.log(linked.indexOf(10)); // -1
	  console.log(linked.size()); // 3
	  console.log(linked.isEmpty()); // false
	  linked.clear();
      console.log(linked.isEmpty()); // true
	})();
複製代碼

2-雙向鏈表

定義雙向鏈表
function DoublyLinkedList() {
  let Node = function(element) {
    this.element = element;
    this.next = null;
    this.prev = null; // 指向前一項的指針
  };
  let length = 0,
    head = null,
    tail = null; // 尾指針

    //此處就是方法,都在下面詳細說明
}
複製代碼

insert實現

相比較於單向鏈表,雙向鏈表比較複雜,它有後驅指針next還有前驅指針prev,那插入元素就要考慮的狀況也多了ui

  • 區別一:咱們多了一個prev,也多一個尾部指針tail(永遠指向鏈表的尾部)

insert思路:this

  • case1: 插入時當position爲0時要分兩種狀況
    • 鏈表爲空:那就直接將head和tail都指向新建項node
    • 鏈表不爲空:那就將新建項node的next指針指向head,而後將head的prev指向node,之後將head指向node。
  • case2: 當position等於length時(從尾部插入):
    • 其實尾部插入比較簡單,直接就是將新建項node的prev指向tail指向的項,將tail直線給的項的next指向新建項node,最後將tail指向node
  • case3: 當position從中間插入時
    • 這個有點難理解,咱們標記1234步,按照順序看代碼,相信能理解其中的意思(綠色的表示要插入的項)
      image

此處還有一個不同的地方,就是多了一個函數find,因爲雙向鏈表,咱們時能夠經過一個指針current來找到前驅結點和後驅結點,(單向鏈表只能訪問後驅結點,因此才須要previous指針來做爲前驅結點的指針)因此咱們在這裏取消了previous指針,而後根據前面的學習,咱們能夠發現有一部分代碼在插入和移除方法中都有,因此咱們將這段代碼抽離出來建立爲find方法spa

<script>
        this.find = function(position) { // 遍歷到position位置
		  let current = head;
          for (let i = 0; i < position; i++) {
            current = current.next;
		  }
		  return current;
		}

		this.insert = function(element, position) { // 指定位置插入值
		  if (position >= 0 && position <= length) {
            let node = new Node(element);
            let current = head,
				previous = null;
			if (position === 0) { // case1: 從鏈表頭部插入
			  if (!head) {
                // 鏈表爲空
				head = node;
			    tail = node;
			  }else {
				// 鏈表不爲空
                node.next = head;
                head.prev = node;
                head = node;
			  }
			}else if (position === length) { // case2: 從鏈表尾部插入
			  node.prev = tail;
			  tail.next = node;
			  tail = node;
			}else { // case3: 從鏈表的中間插入
			  current = this.find(position);
			  // 插入元素
              node.next = current;
              node.prev = current.prev;
              current.prev.next = node;
              current.prev = node;
			}
			length += 1;
		  }else {
			return 0;
		  }
		};
複製代碼

removeAt實現

思路同insert,分三個階段進行移除項的操做,我重點說一下中間移除項的操做,有些地方是經過current指針和previous指針一塊兒來遍歷,而後進行操做,其實previous能夠用current.prev來代替,由於這是雙向鏈表,因此徹底不須要多加一個previous指針。

image

/*
   removeAt思路解析:
   同insert,分三個階段進行移除項的操做
   */
  this.removeAt = function(position) {
    if (position >= 0 && position < length) {
      let node = new Node(),
        current = head;
      if (position === 0) { // 移除第一項
        head = head.next;
        head.prev = null;
        if (length === 1) { // 鏈表只有一項
          tail = null;
        }
      }else if (position === length - 1) { // 移除最後一項
        tail = tail.prev;
        tail.next = null
      }else { // 移除中間的項
        current = this.find(position);
        current.prev.next = current.next;
        current.next.prev = current.prev;
      }
      length -= 1;
      return current.element; // 返回被移除的項
    }else {
      return null;
    }
  };
複製代碼

其餘的方法

關於其餘的方法,我只列出部分,由於其實單向和雙向的主要區別就在於有一個prev的前驅指針,還有一個尾指針tail,在其餘的方法中,咱們只須要注意這兩個點就好,如下代碼均有註釋

this.append = function(element) { // 尾部添加項
    let node = new Node(element),
      current = head;
    if (head === null) {
      head = node;
      tail = node;
    }else {
      // 這裏是和單鏈表不一樣的地方,也就是添加。
      tail.next = node;
      node.prev = tail;
      tail = node;
    }
    length += 1;
  };
複製代碼
this.print = function() { // 輸出鏈表的值-同單向鏈表
    let current = head;
    for (let i = 0; i < length; i++) {
      console.log(`第${i+1}個值:${current.element}`);
      current = current.next;
    }
  };
  this.clear = function() { // 清空鏈表
    let current = head;
    for (let i = 0; i < length; i++) {
      this.removeAt(i);
    }
    length = 0;
    head = null;
    tail = null; // 此處須要將tail指針也賦值爲null
  };

  this.size = function() { // 鏈表的長度-同單向鏈表
    return length;
  };
複製代碼

3-循環鏈表

關於循環鏈表,和單向鏈表和雙向鏈表最大的不一樣就是尾部的next指向head

  • 循環雙向鏈表
    image
  • 循環單向鏈表
    image
相關文章
相關標籤/搜索