1. 程式人生 > >js堆,棧與佇列的區別

js堆,棧與佇列的區別

棧的定義

棧是電腦科學中的一種抽象資料型別,只允許在有序的線性資料集合的一端(稱為堆疊頂端,英語:top)進行加入資料(英語:push)和移除資料(英語:pop)的運算。因而按照後進先出(LIFO, Last In First Out)的原理運作。

棧的常用操作

棧中有兩個基本的操作

  • 推入 :從棧的頂端推入一個數據,依次往下推
  • 彈出 :講棧頂端的資料移除

棧的基本提點就是

  • 先進後廚,後入先進
  • 除了頭尾的節點,每個元素都有一個先驅和一個後繼

對於棧的畫面的理解,可以想象成一個步槍彈夾新增子彈和射擊的過程
彈夾只有一個出入口進行推入和彈出

js模擬實現一個棧

<body style='width: 80%;height:80%; margin: 10% auto;'>
<ul id="stackBox" style="width:100px;border:1px solid #1fb19e;">
</ul>
<div style="width:400px; display:flex;">
    <input id="funStackBox" value="執行函式">
    <div style="margin-left:100px;">
        <button onclick="pushStack()" type='primary'>進棧</button>
        <button onclick="popStack()" type='primary'>出棧</button>
    </div>
</div>
<script>
    // 棧盒子
    let stackBox = document.getElementById('stackBox')
    // 執行函式盒子
    let funStackBox = document.getElementById('funStackBox')

    // 棧類
    class Stack {
        constructor() {
            this.list = []
        }
        // 新增棧
        addStack(val) {
            this.list.push(val)
            let beforeDom = document.getElementById(this.list.length - 1)
            // 新增棧
            let el = document.createElement('li')
            el.id = this.list.length
            el.innerText = this.list[this.list.length - 1]
            stackBox.insertBefore(el, beforeDom)
        }
        // 彈出棧
        popStack() {
            let delDom = document.getElementById(this.list.length)
            stackBox.removeChild(delDom)
            return this.list.pop()
        }
        // 棧的大小
        stackLength() {
            console.log('當前棧大小為'+this.list.length)
            return this.list.length
        }
        // 棧的頂層元素
        stackIsHas() {
            return  !Boolean(this.list.length)?console.log('沒有棧了'):this.list[this.list.length]
        }
    }
    stackOne = new Stack()
    /**
     * @author: 周靖鬆
     * @information: 進棧
     * @Date: 2019-06-10 12:47:16
     */
    function pushStack() {
        stackOne.addStack(funStackBox.value)
        console.log(funStackBox.value, '進棧')
        stackOne.stackLength()
        stackOne.stackIsHas()
    }
    /**
     * @author: 周靖鬆
     * @information: 出棧
     * @Date: 2019-06-10 12:47:16
     */
    function popStack() {
        let popStack = stackOne.popStack(funStackBox.value)
        console.log(popStack, '出棧')
        stackOne.stackLength()
        stackOne.stackIsHas()
    }


</script>

</body>

效果圖如下

棧被建立的時機

上邊說了棧的基本結構和方法,那麼棧被建立的時候又做了什麼事情呢

首先在我們的js在解釋執行程式碼的時候,最先遇到的就是全域性程式碼,所以在一開始的時候首先就會向棧裡邊壓入一個全域性執行上下文

全域性上下文 只有在全域性執行環境被銷燬的時候才會被彈出銷燬
全域性執行上下文 只有一個 就是瀏覽器中的window物件,this預設指向這個全域性物件

然後當執行一個函式的時候,會在開始先建立一個執行上下文壓入棧中,如果裡邊又執行其他的函式的時候,又會建立一個新的執行上下文壓入執行棧中,知道函式執行完畢,就會把函式的上下文從執行棧中彈出

function fun3() {
console.log('3')
}

function fun2() {
    fun3();
}

function fun1() {
    fun2();
}

fun1();

比如說上邊這段程式碼 ,他的進棧出棧順序就是一開始我們放的那兩張圖的效果

function fun1() {
     console.log('1')
}
function fun2() {
     console.log('2')
}
function fun3() {
     console.log('3')
}

如果是這種的話 則會是最下邊有一個全域性的棧,然後三個函式分別進棧出棧

棧中的執行上下文

剛才我們在上邊提到了執行上下文的概念,執行上下文是跟函式相關的,執行上下文分為兩個階段

  • 建立階段
  • 執行階段

首先建立階段

  • 掃描變數和函式(確定變數環境)
  • 確定this指向
  • 確定詞法環境

簡單說一下詞法環境和變數環境的區別,我個人理解的就是說詞法環境是包含變數環境的

在js裡邊原型鏈大家都不陌生 ,js在當前的物件裡邊找不到所使用的屬性的話會去他的上一級去找
直到Object,再找不到就會undefined ,這裡邊 當前物件的作用域就是他的變數環境,而詞法環境則是與之關聯的的執行上下文中宣告的變數

在建立階段 函式的宣告會被儲存在當前的變數環境之中,var的變數的話則會被設定成undefined
,所以我們在宣告之前就可以訪問到var宣告的變數 ,but他是一個undfined

然後就是執行階段了

這個時候已經完成了對所有變數的分配,開始執行程式碼

佇列的定義

佇列是一種比較高效的資料結構,他與棧不同的是,佇列只能在隊尾插入元素,在隊首刪除元素,
佇列用生活中的事物距離的話,大家可以想想一下沙漏,先進入的沙子先出去,後進去的沙子後出去

佇列比棧高效的地方就在於,迴圈的時候,棧會開闢一個新的臨時棧,然後進行排序,再迴圈,最後在確保不打亂原有順序的情況下 排列回去

佇列則不需要這麼多步驟

js模擬佇列實現

佇列常用的一些操作有

  • 向隊尾新增一個元素
  • 刪除隊首元素
  • 顯示所有元素
  • 清空佇列
  • 佇列是否為空

就先拿這些常用的方法實現以下,老規矩,請大家看程式碼

     // 佇列類
    class Queue {
        constructor() {
            this.dataStore = []
        }

        //新增
        addQueue(val) {
            this.dataStore.push(val);
        }


        //刪除佇列首的元素
        delQueue() {
            if (this.queueIsNone()) return console.log('佇列空了');
            else this.dataStore.shift();
        }

        //佇列是否為空
        queueIsNone() {
            return this.dataStore.length == 0
        }

        //檢視所有元素
        showQueue() {
            return this.dataStore.join('\n');
        }

        //清空佇列
        clearQueue() {
            this.dataStor = [];
            this.showQueue()
            console.log('佇列空了')
        }
    }

    // 佇列例項
    let queueOne = new Queue()
    /**
     * @author: 周靖鬆
     * @information: 進隊
     * @Date: 2019-06-11 21:01:28
     */
    function QueueIn (){
        queueOne.addQueue(funStackBox.value)
        console.log(queueOne.showQueue())
    }
    /**
     * @author: 周靖鬆
     * @information: 出隊
     * @Date: 2019-06-11 21:01:35
     */
    function QueueOut (){
        queueOne.delQueue()
        console.log(queueOne.showQueue())
    }
    /**
    * @author: 周靖鬆
    * @information: 清空佇列
    * @Date: 2019-06-11 21:05:35
    */    
    function QueueClear(){
        queueOne.clearQueue()
    }

上邊的程式碼 大家可以自己寫個html試一下哈,html就不寫了直接貼一下效果圖

另外雖然剛才我們再例子中是用資料去模擬的棧和佇列的實現,but 陣列他其實並不是一個棧記憶體,他是一個堆記憶體

堆的定義

若是滿足以下特性,即可稱為堆:是電腦科學中的一種特別的樹狀資料結構。可以給堆中的任意節點新增新的節點(百科全書)

堆和棧的區別

在JS中,每一個數據都需要一個記憶體空間。記憶體空間又被分為兩種,棧記憶體(stack)與堆記憶體(heap)。

  • 棧為自動分配的記憶體空間,它由系統自動釋放
  • 堆是動態分配的記憶體,大小不定也不會自動釋放

也不是說不會自動釋放,堆在沒有引用的時候,下一次垃圾回收機制出現的時候會回收他的記憶體

js 的變數分為基本型別和引用型別

  • 基本型別 (Undefined、Null、Boolean、Number和String)
    基本型別在記憶體中佔據空間小、大小固定 ,他們的值儲存在棧(stack)空間,是按值來訪問

  • 引用型別 (物件、陣列、函式)
    引用型別佔據空間大、大小不固定, 棧記憶體中存放地址指向堆(heap)記憶體中的物件。是按引用訪問的

盜個圖舉個例子

js不允許直接訪問堆記憶體中的位置,因此我們不能直接操作物件的堆記憶體空間,所以棧中存的是一個指向堆記憶體的一個地址

當我們要訪問堆記憶體中的引用資料型別時,實際上我們首先是從棧中獲取了該物件的地址引用(或者地址指標),然後再從堆記憶體中取得我們需要的資料。

今天就堆疊佇列的內容就大概說到這裡 下一篇部落格 在繼續說一下,關於記憶體的回收機制,堆疊溢位,記憶體分配和常用的一些用法,比如排序等等

有什麼說的不對或者不足的地方,請大家批評指