JavaScript-數據結構與算法(一):棧與隊列

《學習JavaScript數據結構與算法》(下文中簡稱《學》)讀書筆記。
文章首發於個人博客javascript

棧是一種聽從後進先出(Last In First Out, LIFO)的有序集合。新添加的元素保存在棧的末尾,稱做棧頂,另外一端叫棧底。java

通俗點講就好像咱進電梯,後進的人先出來(電梯大了順序亂了啥的別計較)。git

建立棧

建立一個類來表示棧,選擇數組來保存棧裏的元素:github

function Stack() {
  this.items = [];
}

實現一個棧,一般要實現如下幾種方法:算法

方法 功能
push() 添加一個(或幾個)新元素到棧頂
pop() 移除棧頂元素,同時返回該元素
peek() 返回棧頂元素
isEmpty() 判空
clear() 清空
size() 棧裏元素個數

實現後的代碼:數組

function Stack() {
  this.items = [];
}

Stack.prototype = {
  push: function (element) {
    this.items.push(element)
  },
  pop: function () {
    return this.items.pop();
  },
  peek: function () {
    return this.items[this.items.length - 1];
  },
  isEmpty: function () {
    return this.items.length === 0;
  },
  size: function () {
    return this.items.length;
  },
  clear: function () {
    this.items = [];
  },
  print: function () {
    console.log(this.items.toString());
  }
};

module.exports = Stack;

使用Stack類

咱們能夠對上面代碼作個測試:數據結構

var Stack = require('./Stack');

var stack = new Stack();

console.log(stack.isEmpty());  // true

stack.push(5);
stack.push(8);
console.log(stack.peek());

stack.push(11);
console.log(stack.size());  // 3
console.log(stack.isEmpty());  // false

stack.push(15);

stack.pop();
stack.pop();
console.log(stack.size());  // 2
stack.print();  // 5, 8

下圖描述了咱們上面測試代碼的操做(圖片來源:《學》):學習

push操做

pop操做

應用示例

十進制轉爲其餘進制測試

轉爲二進制圖解,其餘進制也相似:ui

十進制=>二進制

代碼實現(使用了ES6語法默認參數,也能夠去掉= 2):

var Stack = require('./Stack');

function baseConverter(decNumber, base = 2) { // ES6語法,默認參數
  var remStack = new Stack(),
      rem,
      binaryString = '',
      digits = '0123456789ABCDEF';

  while (decNumber > 0) {
    rem = Math.floor(decNumber % base);
    remStack.push(rem);
    decNumber = Math.floor(decNumber / base);
  }

  while (!remStack.isEmpty()) {
    binaryString += digits[remStack.pop()];
  }

  return binaryString;
}

// test
console.log(baseConverter(233));
console.log(baseConverter(10, 8));
console.log(baseConverter(1000, 16));

判斷給定字符串是不是迴文

let Stack = require('./Stack');

let isPalindrome = (str) => {
  let stack = new Stack();
  
  for (let i = 0; i < str.length; i++) {
    stack.push(str[i]);
  }
  
  let rstr = '';
  while(str.size() > 0) {
    rstr += stack.pop();
  }
  return str === rstr ? true : false;
};

模擬遞歸

// 階乘
function factorial(n) {
  n === 0 && return 1;
  return n * factorial(n - 1);
}
let Stack = require('./Stack');

let fact = (n) => {
  let stack = new Stack();
  
  while (n > 1) {
    stack.push(n--);
  }
  
  let product = 1;
  while (stack.size() > 0) {
    product *= stack.pop();
  }
  return product;
};

隊列

隊列是一種先進先出(First In First Out, FIFO)的有序的數據結構。在尾部添加新元素,並從頂部移除元素。

建立隊列

建立一個類來表示隊列,用數組存儲隊列中元素的數據結構:

function Queue() {
  this.items = [];
}

實現一個隊列,一般要實現如下幾種方法:

方法 功能
enqueue() 向隊列尾部添加一個(或多個)新的項
dequeue() 移除隊列隊首元素並返回
front() 返回隊列中第一個元素
isEmpty() 判空
size()

實現後的代碼:

function Queue() {
  this.items = [];
}

Queue.prototype = {
  enqueue: function (element) {
    this.items.push(element)
  },
  dequeue: function () {
    return this.items.shift();
  },
  front: function () {
    return this.items[0];
  },
  isEmpty: function () {
    return this.items.length === 0;
  },
  size: function () {
    return this.items.length;
  },
  clear: function () {
    this.items = [];
  },
  print: function () {
    console.log(this.items.toString());
  }
};

module.exports = Queue;

使用Queue類

var Queue = require('./Queue');

var queue = new Queue();
console.log(queue.isEmpty());

queue.enqueue('John');
queue.enqueue('Bob');

queue.enqueue('Lily');

queue.print(); // "John", "Bob", "Lily"
console.log(queue.size()); // 3
queue.dequeue();
queue.print(); // "Bob", "Lily"

優先隊列

優先隊列的添加和移除是基於優先級的。顯示中的例子是頭等艙,再有就是急診室等。

實現一個優先隊列,有兩種選項:設置優先級,而後在正確的位置添加元素;或者用入列操做元素,而後按照優先級移除它們。咱們使用第一種方式進行演示:

function PriorityQueue() {
  this.items = [];
}

/**
 * 要添加到隊列的元素及其在隊列中的優先級
 * @param {[type]} element
 * @param {[number]} priority 優先級
 */
function QueueElement(element, priority) {
  this.element = element;
  this.priority = priority;
}

PriorityQueue.prototype = {
  enqueue: function (element, priority) {
    var queueElement = new QueueElement(element, priority);

    if (this.isEmpty()) {
      // 若是隊列爲空,元素直接入列
      this.items.push(queueElement);
    } else {
      var added = false;
      for (var i = 0; i < this.items.length; i++) {
        if (queueElement.priority < this.items[i].priority) { // 最小優先隊列
          // 還能保證優先級相同時也遵循隊列先進先出的原則
          this.items.splice(i, 0, queueElement);
          added = true;
          break;
        }
      }
      // 要添加元素的priority值大於任何已有的元素,把它添加到隊列的末尾
      if (!added) items.push(queueElement);
    }
  }
  // 其餘方法和默認的Queue實現相同
};

測試:

var priorityQueue = new PriorityQueue();
priorityQueue.enqueue('John', 2);
priorityQueue.enqueue('Jack', 1);
priorityQueue.enqueue('Camila', 1);
priorityQueue.print();

下圖能夠看到上面測試代碼的運行結果(圖片來源:《學》):

queue

第一個被添加的元素是優先級爲2的John。由於此前隊列爲空,因此它是隊列中惟一的元素。接下來,添加了優先級爲1的Jack。因爲Jack的優先級高於John,它就成了隊列中的第一個元素。 而後, 添加了優先級也爲1的Camila。 Camila的優先級和Jack相同,因此它會被插入到Jack以後(由於Jack先被插入隊列); Camila的優先級高於John,因此它會被插入到John以前。

循環隊列——擊鼓傳花

擊鼓傳花遊戲:人們圍成一個圈,把花盡快傳給旁邊的人。某一時刻傳花中止,此時花在誰手裏誰就退出圓圈結束遊戲。重複此過程,直到只剩一個勝者。

var Queue = require('./Queue');
function hotPotato(nameList, num) {
  var queue = new Queue();

  for (var i = 0; i < nameList.length; i++) {
    // 遊戲者所有入隊列
    queue.enqueue(nameList[i]);
  }

  var eliminated = '';
  while (queue.size() > 1) {
    for (var i = 0; i < num; i++) {
      // 從隊列開頭移除一項,再將其添加到隊列末尾,模擬擊鼓傳花(若是你把花傳給了旁邊的人,你被淘汰的威脅馬上就解除了)
      queue.enqueue(queue.dequeue());
    }
    // 傳遞次數達到給定數字,此時拿花的人被淘汰
    eliminated = queue.dequeue();
    console.log(eliminated + '在擊鼓傳花遊戲中被淘汰');
  }

  return queue.dequeue();
}

測試代碼:

var names = ['John', 'Jack', 'Camila', 'Ingrid', 'Carl'];
var winner = hotPotato(names, 7);
console.log('the winner is : ' + winner);

輸出:

Camila在擊鼓傳花遊戲中被淘汰
Jack在擊鼓傳花遊戲中被淘汰
Carl在擊鼓傳花遊戲中被淘汰
Ingrid在擊鼓傳花遊戲中被淘汰
the winner is : John

下圖模擬了這個輸出過程(圖片來源:《學》):

循環隊列

相關文章
相關標籤/搜索