Javascript 中的數據結構

Javascript 中的數據結構

這段時間得空將 Javascript 中的數據結構整理了一下, 雖然該篇幅有點簡單, 並且有點長, 若是您以爲這些對您沒什麼幫助, 能夠自行跳過. 但願此文能夠幫助到那些須要幫助的人.前端

代碼行間加了一些我本身理解的註釋, 若是有不對的地方, 還望各位大佬海涵. 也能夠聯繫我而後加以改正, 共同進步node

原文連接git

列表

class List {
  constructor() {
    this.listSize = 0
    this.pos = 0 // 指針
    this.dataStore = []
  }

  length() {
    return this.listSize
  }

  // 清除列表
  clear() {
    this.dataStore = []
    this.listSize = this.pos = 0
  }

  // 判斷給定值是否在列表中
  contains(value) {
    if (this.find(value) > -1) {
      return true
    }
    return false
  }

  toString() {
    return this.dataStore.toString()
  }

  // 獲取當前指針的元素
  getElement() {
    return this.dataStore[this.pos]
  }

  // 插入到某元素以後
  insert(value, after) {
    const idx = this.find(after)
    if (idx > -1) {
      this.dataStore.splice(idx+1, 0, value)
      ++this.listSize
      return true
    }

    return false
  }

  // 添加元素到列表末尾
  append(value) {
    this.dataStore[this.listSize++] = value
  }


  find(value) {
    // return this.dataStore.indexOf(value)
    for (let i = 0; i < this.dataStore.length; i++) {
      if (this.dataStore[i] === value) {
        return i
      }
    }
    return -1
  }

  // 移除元素
  remove(value) {
    const idx = this.find(value)
    if (idx > -1) {
      this.dataStore.splice(idx, 1)
      --this.listSize
      return true
    }
    return false
  }

  // 指針頭
  front() {
    this.pos = 0
  }

  // 指針尾
  end() {
    this.pos = this.listSize - 1
  }

  // 前移指針
  prev() {
    if (this.pos > 0) {
      --this.pos
    }
  }

  // 後移指針
  next() {
    if (this.pos < this.listSize - 1) {
      ++this.pos
    }
  }

  // 當前指針
  currPos() {
    return this.pos
  }

  moveTo(position) {
    this.pos = position
  }
}

const names = new List();
names.append("Clayton");
names.append("Raymond");
names.append("Cynthia");
names.append("Jennifer");
names.append("Bryan");
names.append("Danny");
names.front();
console.log(names.getElement()); // Clayton
names.next();
console.log(names.getElement()); // Raymond
names.next();
names.next();
names.prev();
console.log(names.getElement()); // Cynthia

for(names.front(); names.currPos() < names.length(); names.next()) {
  console.log(names.getElement());
}
複製代碼

棧是一種先進後出的數據結構github

class Stack {
  constructor() {
    this.dataStore = []
    this.top = 0
  }

  // 往棧頂添加元素
  push(value) {
    this.dataStore[this.top++] = value
  }

  pop() {
    return this.dataStore[--this.top]
  }

  // 返回棧頂元素
  peek() {
    return this.dataStore[this.top - 1]
  }

  length() {
    return this.top
  }

  // 清空棧
  clear() {
    this.top = 0
  }
}

const s = new Stack();
s.push("David");
s.push("Raymond");
s.push("Bryan");
console.log("length: " + s.length()); // length: 3
console.log(s.peek()); // Bryan
var popped = s.pop();
console.log("The popped element is: " + popped); // The popped element is Bryan
console.log(s.peek()); // Raymond
s.push("Cynthia");
console.log(s.peek()); // Cynthia
s.clear();
console.log("length: " + s.length()); // length: 0
console.log(s.peek()); // undefined
s.push("Clayton");
console.log(s.peek()) // Clayton
複製代碼

棧的應用算法

  1. 進制轉換
function mulBase(num, base) {
  const s = new Stack()
  while (num > 0) {
    s.push(num % base)
    num = Math.floor(num / base)
  }

  let res = ''

  while(s.length() > 0) {
    res += s.pop()
  }

  return res
}
複製代碼
  1. 迴文字符串
function isPalindrome(word) {
  const s = new Stack()
  for (let i = 0; i < word.length; i++) {
    s.push(word[i])
  }

  let newWord = ''

  while (s.length() > 0) {
    newWord += s.pop()
  }

  if (newWord === word) {
    return true
  }
  return false
}
複製代碼

隊列

隊列是一種先進先出的數據結構shell

class Queue {
  constructor() {
    this.dataStore = []
  }

  // 入隊
  enqueue(element) {
    this.dataStore.push(element)
  }

  // 出隊
  dequeue() {
    return this.dataStore.shift()
  }

  front() {
    return this.dataStore[0]
  }

  back() {
    return this.dataStore[this.dataStore.length - 1]
  }

  toString() {
    return this.dataStore.toString()
  }

  empty() {
    return this.dataStore.length === 0
  }
}

var q = new Queue();
q.enqueue("Meredith");
q.enqueue("Cynthia");
q.enqueue("Jennifer");
console.log(q.toString());
q.dequeue();
console.log(q.toString());
console.log("Front of queue: " + q.front());
console.log("Back of queue: " + q.back());
複製代碼

雙端隊列數組

class Deque {
  /** * 這是雙端隊列 (即兩個端部:首部和尾部) */
  constructor() {
    this.items = new Array()
  }

  isEmpty() {
    return this.items.length === 0
  }

  size() {
    return this.items.length
  }

  // 在後面添加
  addRear(newdata) {
    this.items.push(newdata)
  }

  // 在前面添加
  addFront(newdata) {
    this.items.unshift(newdata)
  }

  // 在後面刪除
  removeRear() {
    return this.items.pop()
  }

  // 在前面刪除
  removeFront() {
    return this.items.shift()
  }
}
複製代碼

應用微信

  1. 迴文檢查
function palChecker(str) {
  let deque = new Deque()
  for (let i = 0; i < str.length; i++) {
      deque.addRear(str[i])
  }
  
  while (deque.size() > 1) {
      let rear = deque.removeRear()
      let front = deque.removeFront()
      
      if (rear !== front) {
          return false
      }
  }
  return true
}

console.log(palChecker('ababcbaba'))
複製代碼

鏈表

  1. 單向鏈表
class Node {
  constructor(element) {
    this.element = element
    this.next = null
  }
}

class LList {
  constructor() {
    this.head = new Node('head')
  }

  find(item) {
    let currNode = this.head
    while (currNode.element !== item) {
      currNode = currNode.next
    }
    return currNode
  }

  insert(newElement, item) {
    const newNode = new Node(newElement)
    const current = this.find(item)
    newNode.next = current.next
    current.next = newNode
  }

  findPrevious(item) {
    let currNode = this.head
    while (currNode.next != null && currNode.next.element !== item) {
      currNode = currNode.next
    }
    return currNode
  }

  remove(item) {
    const prevNode = this.findPrevious(item)
    if (prevNode != null) {
      prevNode.next = prevNode.next.next
    }
  }

  display() {
    let currNode = this.head
    while (currNode.next != null) {
      console.log(currNode)
      currNode = currNode.next
    }
  }
}

var cities = new LList();
cities.insert("Conway", "head");
cities.insert("Russellville", "Conway");
cities.insert("Carlisle", "Russellville");
cities.insert("Alma", "Carlisle");
cities.display();
console.log('===========');
cities.remove("Carlisle");
cities.display();
複製代碼
  1. 雙向鏈表
class Node {
  constructor(element) {
    this.element = element
    this.next = null
    this.prev = null
  }
}

class LList {
  constructor() {
    this.head = new Node('head')
  }

  findLast() {
    let currNode = this.head
    while (currNode.next != null) {
      currNode = currNode.next
    }
    return currNode
  }

  dispReverse() {
    let currNode = this.findLast()
    while (currNode.prev != null) {
      console.log(currNode.element)
      currNode = currNode.prev
    }
  }

  find(item) {
    let currNode = this.head
    while (currNode.element !== item) {
      currNode = currNode.next
    }
    return currNode
  }

  remove(item) {
    let currNode = this.find(item)
    if (currNode.next != null) {
      currNode.prev.next = currNode.next
      currNode.next.prev = currNode.prev
      currNode.next = null
      currNode.prev = null
    }
  }

  display() {
    let currNode = this.head
    while (currNode.next != null) {
      console.log(currNode.next.element)
      currNode = currNode.next
    }
  }

  // 每次都是在末尾插入
  insert(newElement, item) {
    let currentNode = this.find(item)
    const newNode = new Node(newElement)
    newNode.next = currentNode.next
    newNode.prev = currentNode
    currentNode.next = newNode
  }
}

var cities = new LList();
cities.insert("Conway", "head");
cities.insert("Russellville", "Conway");
cities.insert("Carlisle", "Russellville");
cities.insert("Alma", "Carlisle");
cities.display();
console.log('======');
cities.remove("Carlisle");
cities.display();
console.log('======');
cities.dispReverse();
複製代碼
  1. 循環鏈表
// 和單鏈表不一樣之處
class LList {
  constructor() {
    this.head = new Node('head')
    this.head.next = head
  }
  // ... 
  display() {
    let currNode = this.head
    while (currNode.next != null && currNode.next.element !== 'head') {
      console.log(currNode.next.element)
      currNode = currNode.next
    }
  }
  // ...
}
複製代碼
  1. 雙向循環鏈表 和循環鏈表同樣, 尾的下一個指向頭一個, 能夠本身實現一下

字典

class Dictionary {
  constructor() {
    this.dataStore = new Array()
  }

  add(key, value) {
    this.dataStore[key] = value
  }

  find(key) {
    return this.dataStore[key]
  }

  remove(key) {
    delete this.dataStore[key]
  }

  showAll() {
    for (let key of Object.keys(this.dataStore)) {
      console.log(`${key} -> ${this.dataStore[key]}`)
    }
  }

  count() {
    let n = 0
    for (let key of Object.keys(this.dataStore)) {
      ++n
    }
    return n
  }

  clear() {
    for (let key of Object.keys(this.dataStore)) {
      delete this.dataStore[key]
    }
  }
}

var pbook = new Dictionary();
pbook.add("Mike","123");
pbook.add("David", "345");
pbook.add("Cynthia", "456");
console.log("David's extension: " + pbook.find("David"));
pbook.remove("David");
pbook.showAll();
console.log(pbook.count())
複製代碼

散列

散列後的數據能夠快速地插入或取用. 散列使用的數據結構叫散列表. 在散列表上插入, 刪除和取用數據都很是快, 可是對於查找操做來講卻效率低下.數據結構

class HashTable {
  constructor() {
    this.table = new Array(137);
  }

  put(data) {
		const pos = this.betterHash(data);
    this.table[pos] = data;
	}
	
	get(key) {
		return this.table[this.betterHash(key)]
	}

  simpleHash(data) {
    let total = 0;
    for (let i = 0; i < data.length; i++) {
      total += data.charCodeAt(i);
    }
    return total % this.table.length;
  }

  showDistro() {
    for (let i = 0; i < this.table.length; i++) {
      if (this.table[i] != null) {
        console.log(i + ": " + this.table[i]);
      }
    }
  }

  betterHash(data) {
    const H = 31;
    var total = 0;
    for (var i = 0; i < data.length; ++i) {
      total += H * total + data.charCodeAt(i);
    }
    return total % this.table.length;
	}
	
	buildChains() {
		for (let i = 0; i < this.table.length; i++) {
			this.table[i] = new Array()
		}
	}
}

const someNames = ["David", "Jennifer", "Donnie", "Raymond", "Cynthia", "Mike", "Clayton", "Danny", "Jonathan"]

const hTable = new HashTable()

for (let i = 0; i < someNames.length; i++) {
	hTable.put(someNames[i])
}

hTable.showDistro()
複製代碼

集合

集合是一種無序的而且不重複的數據結構app

class Set {
	constructor() {
		this.dataStore = []
	}

	add(data) {
		if (this.dataStore.indexOf(data) < 0) {
			this.dataStore.push(data)
			return true
		} else {
			return false
		}
	}

	remove(data) {
		const pos = this.dataStore.indexOf(data)
		if (pos > -1) {
			this.dataStore.splice(pos, 1)
			return true
		} else {
			return false
		}
	}

	show() {
		return this.dataStore
	}

	size() {
		return this.dataStore.length
	}

	contains(data) {
		if (this.dataStore.indexOf(data) > -1) {
			return true
		} else {
			return false
		}
	}

	// 並集
	union(set) {
		const tempSet = new Set()
		for (let i = 0; i < this.dataStore.length; i++) {
			tempSet.add(this.dataStore[i])
		}

		for (let i = 0; i < set.dataStore.length; i++) {
			if (!tempSet.contains(set.dataStore[i])) {
				tempSet.dataStore.push(set.dataStore[i])
			}
		}
		return tempSet
	}

	// 交集
	intersect(set) {
		let tempSet = new Set()
		for (let i = 0; i < this.dataStore.length; i++) {
			if (set.contains(this.dataStore[i])) {
				tempSet.add(this.dataStore[i])
			}
		}
		return tempSet
	}

	// 是不是 set 的子集
	subset(set) {
		if (this.size() > set.size()) {
			return false
		} else {
			for (let member of this.dataStore) {
				if (!set.contains(member)) {
					return false
				}
			} 
		}
		return true
	}

	// 差集
	difference(set) {
		let tempSet = new Set()
		for (let i = 0; i < this.dataStore.length; i++) {
			if (!set.contains(this.dataStore[i])) {
				tempSet.add(this.dataStore[i])
			}
		}
		return tempSet
	}
}

const names = new Set()
names.add("David");
names.add("Jennifer");
names.add("Cynthia");
names.add("Mike");
names.add("Raymond");
if (names.add('Mike')) {
	console.log('Mike added')
} else {
	console.log('Can not add Mike, must already be in set')
}

console.log(names.show())
複製代碼

二叉樹和二叉查找樹

樹是一種非線性數據結構 在二叉樹上進行查找很是快(而在鏈表上查找則不是這樣), 爲二叉樹添加或刪除元素也很是快(而對數組執行添加或刪除操做則不是這樣)

class Node {
	constructor(data, left, right) {
		this.data = data
		this.left = left
		this.right = right
	}

	show() {
		return this.data
	}
}

// 二叉查找樹
class BST {
	constructor() {
		this.root = null
	}

	// 插入
	insert(data) {
		const n = new Node(data, null, null)
		if (this.root == null) {
			this.root = n
		} else {
			let current = this.root
			let parent
			while (true) {
				parent = current
				if (data < current.data) {
					// 左子樹
					current = current.left
					if (current == null) {
						parent.left = n
						break
					}
				} else {
					// 右子樹
					current = current.right
					if (current == null) {
						parent.right = n
						break
					}
				}
			}
		}
	}

	// 前序遍歷 根->左->右
	preOrder(node) {
		if (node != null) {
			console.log(node.show() + ' ')
			this.preOrder(node.left)
			this.preOrder(node.right)
		}
	}

	// 中序遍歷 左->根->右
	inOrder(node) {
		if (node != null) {
			this.inOrder(node.left)
			console.log(node.show() + ' ')
			this.inOrder(node.right)
		}
	}

	// 後序遍歷 左->右->根
	postOrder(node) {
		if (node != null) {
			this.postOrder(node.left)
			this.postOrder(node.right)
			console.log(node.show() + '')
		}
	}

	// 查找最小值
	getMin() {
		let current = this.root
		while (current.left != null) {
			current = current.left
		}

		return current.data
	}

	// 查找最大值
	getMax() {
		let current = this.root
		while (current.right != null) {
			current = current.right
		}
		return current.data
	}

	// 查找給定值的節點
	find(data) {
		let current = this.root
		while (current != null) {
			if (current.data === data) {
				return current
			} else if (data < current.data) {
				current = current.left
			} else {
				current = current.right
			}
		}
		return null
	}
	
	// 刪除節點
	// 分幾種狀況
	// 1. 刪除的節點沒有子節點
	// 2. 刪除的節點有一個子節點
	// 3. 刪除的節點有兩個子節點
	remove(data) {
		this.root = this.removeNode(this.root, data)
	}

	// remove node
	removeNode(node, data) {
		if (node == null) {
			return null
		}
		if (data === node.data) {
			if (node.left == null && node.right == null) {
				return null
			}

			if (node.left == null) {
				return node.right
			}

			if (node.right == null) {
				return node.left
			}
			// 有兩個子節點, 由於左節點都是比他自己小的, 因此咱們只須要找到右節點中最小的那個
			let tempNode = this.getSmallest(node.right)
			node.data = tempNode.data // 改變要刪除的node的值爲右子樹中最小的那個值
			node.right = this.removeNode(node.right, tempNode.data) // 將node的右子樹從新賦值爲 node.right, 而且node.right 中去執行刪除開始找出的那個最小的值的節點, 也就是 tempNode
			return node
		} else if (data < node.data) {
			// 若是當前要移除的節點的值比 node節點的值小的, 就據需往左作深度遍歷
			node.left = this.removeNode(node.left, data)
			return node
		} else {
			// 若是當前要移除的節點的值比 node節點的值大的, 就據需往右作深度遍歷
			node.right = this.removeNode(node.right, data)
			return node
		}
	}

	getSmallest(node) {
		if (node) {
			// 找最小的話, 最節點的最下面一層就是最小的
			while (node && node.left != null) {
				node = node.left
			}
			return node
		}
		return null
	}

}

const nums = new BST();
nums.insert(23);
nums.insert(45);
nums.insert(16);
nums.insert(37);
nums.insert(3);
nums.insert(99);
nums.insert(22);
nums.insert(88);
nums.insert(77);

nums.remove(45)

console.log(nums.find(77))
複製代碼

應用: 好比說計算學生的成績, 若是成績不存在則加入 BST, 存在, 則 count + 1

class Node {
  constructor() {
    ...
    this.count = 1
  }
}
class BST {

  insert(data) {
    const node = this.find(data)
    if (node) {
      // 若是node節點已經存在的話, count + 1
      node.count++
      return
    }
    ...
  }
}

const nums = new BST();
nums.insert(23);
nums.insert(23);
nums.insert(16);

console.log(nums) // 能夠看到count = 2
/* BST { root: Node { data: 23, left: Node { data: 16, left: null, right: null, count: 1 }, right: null, count: 2 } } */
複製代碼

class Graph {
  constructor(vertices) {
    this.vertices = vertices;
    this.adj = new Map(); // adjacent list 鄰近的點
  }

  // 添加頂點
  addVertex(v) {
    this.adj.set(v, []);
  }

  // 添加頂點之間的關聯 / 邊
  addEdge(v, w) {
    // 你中有我, 我中有你
    this.adj.get(v).push(w);
    this.adj.get(w).push(v);
  }

  // 打印圖
  printGraph() {
    const get_keys = this.adj.keys();

    for (const key of get_keys) {
      const get_values = this.adj.get(key);
      let conc = "";

      for (const value of get_values) {
        conc += value + " ";
      }
      console.log(key + " -> " + conc);
    }
  }

  // 深度優先搜索 Depth First Search
  dfs(s) {
		// create a this.visited array 建立一個頂點訪問列表
		let visited = [];
    for (let i = 0; i < this.vertices; i++) {
      visited[i] = false; // 默認爲false
    }
		this.dfsUtil(s, visited)    
	}
	
	dfsUtil(vert, visited) {
		visited[vert] = true;
    console.log(vert);
    const get_neighbours = this.adj.get(vert); // 獲取關聯點

    for (const i in get_neighbours) {
      const value = get_neighbours[i];

      if (!visited[value]) {
        // 遞歸調用
        this.dfsUtil(value, visited);
      }
    }
	}

  // 廣度優先搜索 Breadth First Search
  bfs(s) {

		let visited = [];
    for (let i = 0; i < this.vertices; i++) {
      visited[i] = false; // 默認爲false
		}
    let q = [];
    
    visited[s] = true;
    q.push(s); // 入隊

    while (q.length > 0) {
      const getQueueElement = q.shift(); // 出隊
      console.log(getQueueElement);

      const get_list = this.adj.get(getQueueElement); // 獲取關聯點 A: B D E, B: A C, C: B E F, D: A E, E: A D F, F: E C
      for (let i in get_list) {
        const neigh = get_list[i];
        // 未訪問
        if (!visited[neigh]) {
          // 入隊, 並標記爲訪問
          visited[neigh] = true;
          q.push(neigh);
        }
      }
    }
  }

  // 最短路徑?
  shortestPath(s, t) {
    // ? 雖然說知道使用廣度優先遍歷去實現, 可是還沒想好怎麼去實現
  }
}

const g = new Graph(6);
const vertices = ["A", "B", "C", "D", "E", "F", "G", "H"];

// adding vertices 添加頂點
for (var i = 0; i < vertices.length; i++) {
  g.addVertex(vertices[i]);
}

// adding edges 添加點與點之間的關聯
g.addEdge("A", "B");
g.addEdge("A", "D");
g.addEdge("A", "E");

g.addEdge("B", "C");

g.addEdge("D", "E");

g.addEdge("E", "F");
g.addEdge("E", "C");

g.addEdge("G", "F");
g.addEdge("G", "H");
g.addEdge("H", "C");

g.printGraph()

// console.log(g.shortestPath('A', 'H'))
複製代碼

排序

  1. 冒泡排序 思想: 前一個和後一個比, 前一個 > 後一個數 ? 兩數交換 : 兩數位置不動
function bubbleSort(arr) {
  for (let i = arr.length - 1; i > 1 ; i--) {
    for (let j = 0; j < i; j++) {
      if (arr[j] > arr[j+1]) {
        [arr[j], arr[j+1]] = [arr[j+1], arr[j]]
      }
    }
  }
  return arr
}

const arr = [1, 10, 2, 3, 5, 4, 8, 0, 7, 6]

console.log(bubbleSort(arr))
複製代碼
  1. 選擇排序 思想: 將第一個數和剩下的數進行比對,檢查完全部的元素後, 最小的元素會被放置在第一個位置, 而後從第二個開始重複以前的操做
// 第一種, 先找出最小值
function selectSort(arr) {
  for (let i = 0; i < arr.length - 1; i++) {
    let min_index = i // 默認第一個最小, 第二次就是第二個最小, 因此 min_index = i

    // 找出最小值的index
    for (let j = i + 1; j < arr.length; j++) {
      if (arr[min_index] > arr[j]) {
        min_index = j
      }
    }
    // 將最小值放置第一個
    [arr[min_index], arr[i]] = [arr[i], arr[min_index]]
    console.log('每次比較的arr爲:', arr) // 能夠看到每次將剩下的沒有進行比較的最小值放置在未比較的最前端
  }
  return arr
}

const arr = [1, 10, 2, 3, 5, 4, 8, 0, 7, 6]

console.log(selectSort(arr))

// 第二種, 先找出最大值
function selectSort(arr) {
  for (let i = arr.length - 1; i > 0; i--) {
    let max = 0
    // 找出最大值的index
    for (let j = 1; j < i + 1; j++) {
      if (arr[j] > arr[max]) {
        max = j
      }
    }
    // 交換
    [arr[i], arr[max]] = [arr[max], arr[i]]
    console.log('每次比較的arr爲:', arr) // 能夠看到每次將剩下的沒有進行比較的最大值放置在未比較的最末端
  }

  return arr
}
複製代碼

冒牌排序和選擇排序比較的次數是同樣的. 不一樣在於選擇排序交換的次數大大減小了, 每次循環只交換一次, 而冒泡排序每次循環交換 (length - 1) 次

  1. 插入排序 思想: Imagine你正在排序一副打亂的牌, 在排序下一張以前, 你會先看一下這張牌的值面是多少, 而後插入到相應的位置, 這個位置前面的值比它小, 後面的值比它大. 這就是插入排序.
function insertSort(arr) {
  for (let i = 1; i < arr.length; i++) {
    let currVal = arr[i] // 默認第一個已經排好序
    let pos = i

    // 將下一個要排序的值和當前值比較, 下一個要排序的值比當前值小, 則交換
    while (pos > 0 && arr[pos - 1] > currVal) {
      arr[pos] = arr[pos - 1]
      pos--
    }

    // 找到了正確的位置以後, 進行賦值
    arr[pos] = currVal
  }
  return arr
}

const arr = [1, 10, 2, 3, 5, 4, 8, 0, 7, 6]

console.log(insertSort(arr))
複製代碼

運行時間比較 冒泡排序 > 選擇排序 > 插入排序

  1. 希爾排序 是插入排序一種更高效的改進版本 思想: 經過定義一個間隔序列來表示在排序過程當中進行比較的元素之間有多遠的間隔。咱們能夠動態定義間隔序列
function shellSort(arr) {
  let gap = Math.floor(arr.length / 2) // 定義間隔
  while (gap > 0) {
    // 間隔 gap 個 進行比較
    for (let i = gap; i < arr.length; i++) {
      let j = i
      // 好比length爲10, 那就是0和5比, 1和6比, 2和7比, 依次類推
      // 若是0位置上的數比5位置上的數大, 二者進行交換
      while (j >= gap && arr[j - gap] > arr[j]) {
        [arr[j-gap], arr[j]] = [arr[j], arr[j-gap]]
        // 這個減的意思是: 假如 gap = 2, 一開始 0和2 比較了,沒交換, 我在2和4比較的時候,發生了交換. 這時候你須要再回過頭去比較 0和2, 由於當2和4交換以後, 有可能如今的0和2須要進行交換了.
        // 總結就是, 每次交換以後, 你的保證前面的順序是正確的, 否則下次就不對了
        j -= gap
      }
    }
    // 將間隔縮小
    gap = Math.floor(gap / 2)
  }
  return arr
}
// 當間隔縮小的時候, 比較的次數就多了, 可是是基於前面一部分已排好序的基礎上進行的. 這也是爲何比插入排序高效所在

const arr = [1, 10, 2, 3, 5, 4, 8, 0, 7, 6]

console.log(shellSort(arr))
複製代碼
  1. 歸併排序 思想: 把一系列排好序的子序列合併成一個大的完整有序序列.
function mergeSort(arr) {
  if (arr.length <= 1) {
    return arr
  }
  // 不斷的將數組進行二等分, 直至最小(也就是一個元素)
  let mid = Math.floor(arr.length / 2)
  let left = mergeSort(arr.slice(0, mid))
  let right = mergeSort(arr.slice(mid))

  return merge(left, right)
}

function merge(left, right) {
  let result = []
  while (left.length && right.length) {
    // 每次比較左邊第一個和右邊第一個, 每次往result裏面push小的那一個
    if (left[0] < right[0]) {
      result.push(left.shift())
    } else {
      result.push(right.shift())
    }
  }

  return result.concat(left, right)
}


const arr = [1, 10, 2, 3, 5, 4, 8, 0, 7, 6]

console.log(mergeSort(arr))
複製代碼
  1. 快速排序 快速排序是處理大數據集最快的排序算法之一. 思想: 它是一種分而治之的算法, 經過遞歸的方式將數據依次分解爲包含較小元素和較大元素的不一樣子序列.
// 簡單的版本
// 這個會浪費空間
function quickSort(arr) {
  // 遞歸結束條件
  if (arr.length <= 1) return arr
  // 基準值
  let midValue = arr.splice(0, 1)[0]
  let left = []
  let right = []

  for (let i = 0; i < arr.length; i++) {
    // 比基準值大放右邊, 小放左邊
    if (arr[i] > midValue) {
      right.push(arr[i])
    } else {
      left.push(arr[i])
    }
  }

  return quickSort(left).concat(midValue, quickSort(right))
}

// 第二版本
function quickSort(arr, start=0, end=arr.length-1) {
  // 遞歸結束條件
  if (start > end) return 
  let midValue = arr[start] // 基準值
  let low = start // 低位指針
  let high = end // 高位指針

  while (low < high) {
    // 先從高位開始, 當高位的值大於基準值, 高位指針往左移
    while (low < high && arr[high] >= midValue) {
      high--
    }
    // 高位的值小於基準值, 將低位的值賦值爲高位的值
    arr[low] = arr[high]

    // 當低位的值小於基準值, 低位指針往右移
    while (low < high && arr[low] < midValue) {
      low++
    }
    // 低位的值大於基準值, 將改成的值賦值爲低位的值
    arr[high] = arr[low]
  }

  // 當while循環結束的時候, 低指針指向的數咱們賦值爲基準值
  // 這時候是 low === high 的狀況
  // arr[high] = midValue 
  arr[low] = midValue
  // 遞歸調用, 每完成一次遞歸, 左邊的值都會比基準值小, 右邊的值都會比基準值大.
  quickSort(arr, start, low - 1)
  quickSort(arr, low + 1, end)
}

const arr = [1, 10, 2, 3, 5, 4, 8, 0, 7, 6]

quickSort(arr)

console.log(arr)

// 注意: 選start爲基準值, 必須從右邊開始遍歷. 選end爲基準值, 必須從左邊開始遍歷
複製代碼

思考: 爲何要從對面開始呢??? 假設咱們的數組爲: [6, 1, 2, 7, 9], 選start的爲基準值, 當從左邊開始的時候, 當 low === high 的時候, low和基準值進行交換, 這時候基準值是6, low === high 的時候, low和high都停留在7那個位置, 當6和7進行交換的時候, 這時候數組=[7, 1, 2, 6, 9], 你會發現,左邊的值有比基準值6小的了. 因此這是爲何要從對面開始找的緣由.

檢索算法

在列表中查找數據有兩種方式: 順序查找和二分查找. 順序查找適用於元素隨機排列的列表, 二分查找適用於元素已排序的列表. 二分查找效率更高, 可是你必須在進行找以前花費額外的時間將列表中的元素排序.

順序查找:

// 這個就是 Array.prototype.indexOf() 方法
function seqSearch(arr, data) {
  for (let i = 0; i < arr.length; i++) {
    if (arr[i] === data) {
      return i
    }
  }
  return -1
}

const arr = [1, 10, 2, 3, 5, 4, 8, 0, 7, 6]

console.log(seqSearch(arr, 9))
複製代碼

查找最小(大)值:

// Math.min.apply(null, [1, 10, 2, 3, 5, 4, 8, 0, 7, 6])
function findMin(arr) {
  let min = arr[0]

  for (let i = 1; i < arr.length; i++) {
    if (arr[i] < min) {
      min = arr[i]
    }
  }
  return min
}

const arr = [1, 10, 2, 3, 5, 4, 8, 0, 7, 6]

console.log(findMin(arr))

// Math.max.apply(null, [1, 10, 2, 3, 5, 4, 8, 0, 7, 6])
function findMax(arr) {
  let max = arr[0]

  for (let i = 0; i < arr.length; i++) {
    if (arr[i] > max) {
      max = arr[i]
    }
  }
  return max
}

const arr = [1, 10, 2, 3, 5, 4, 8, 0, 7, 6]

console.log(findMax(arr))
複製代碼

二分法查找 若是你要查找的數據是有序的, 二分法查找算法比順序查找算法更高效.

function binSearch(arr, data) {
  let high = arr.length - 1
  let low = 0

  while (low <= high) {
    let mid = Math.floor((low + high) / 2)
    if (arr[mid] < data) {
      low = mid + 1
    } else if (arr[mid] > data) {
      high = mid - 1
    } else {
      return mid
    }
  }
  return -1
}

const arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

console.log(binSearch(arr, 8))
複製代碼

高級算法

動態規劃 動態規劃有時被認爲是一種與遞歸相反的技術。 遞歸是從頂部開始將問題分解, 經過解決掉全部分解出小問題的方式, 來解決整個問題。 動態規劃解決方案從底部開始解決問題, 將全部小問題解決掉, 而後合併成一個總體解決方案, 從而解決掉整個大問題。 本章與本書其餘多數章節的不一樣在於, 這裏沒有討論除數組之外其餘形式的數據結構。 有時, 若是使用的算法足夠強大, 那麼一個簡單的數據結構就足以解決問題。

斐波那契數列

使用遞歸

function fib(n) {
  if (n < 2) return n
  return fib(n-1) + fib(n-2)
}

fib(10) // 55

// 遞歸的最大問題所在就是有太多的重複計算

// 優化下
function fib(n) {
  if (n < 2) return n

  // 使用map保存以前已經存儲的值
  const arr = new Map()

  if (arr.has(n)) {
    return arr[n]
  } else {
    arr.set(n, fib(n-1) + fib(n-2))
    return arr.get(n)
  }
}
複製代碼

使用動態規劃

function dynFib(n) {
  let val = []
  for (let i = 0; i <= n; i++) {
    val[i] = 0
  }

  if (n === 0 || n === 1) {
    return n
  } else {
    val[1] = 1
    val[2] = 2

    for (var i = 3; i <= n; i++) {
      val[i] = val[i - 1] + val[i - 2]
    }
    return val[n - 1]
  }
}

console.log(dynFib(10))

console.time('dynFib running time')
dynFib(10)
console.timeEnd('dynFib running time')

// 你能夠本身去用 console.time 和 conslole.timeEnd 去測試下程序運算耗時. 動態規劃比遞歸的耗時要小的多


// 另外一種
function iterFib(n) {
  let last = 1
  let nextLast = 1
  let result = 1

  if (n === 0) {
    return 0
  }

  for (let i = 2; i < n; i++) {
    result = last + nextLast
    nextLast = last
    last = result
  }
  return last
}
複製代碼

最後

最近加入了找工做的潮流,若是各位大佬能幫忙內推的話,小的在這感激涕零.

聯繫方式

微信: tears145

郵箱:lenlch@163.com

相關文章
相關標籤/搜索