日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

【数据结构与算法】栈与队列

發布時間:2023/12/10 编程问答 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【数据结构与算法】栈与队列 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一、什么是棧?

1.后進者先出,先進者后出,這就是典型的“棧”結構。
2.從棧的操作特性來看,是一種“操作受限”的線性表,只允許在端插入和刪除數據。

二、為什么需要棧?

1.棧是一種操作受限的數據結構,其操作特性用數組和鏈表均可實現。
2.但,任何數據結構都是對特定應用場景的抽象,數組和鏈表雖然使用起來更加靈活,但卻暴露了幾乎所有的操作,難免會引發錯誤操作的風險。
3.所以,當某個數據集合只涉及在某端插入和刪除數據,且滿足后進者先出,先進者后出的操作特性時,我們應該首選棧這種數據結構。

三、如何實現棧?

1.棧的API
public class Stack {
//壓棧
public void push(Item item){}
//彈棧
public Item pop(){}
//是否為空
public boolean isEmpty(){}
//棧中數據的數量
public int size(){}
//返回棧中最近添加的元素而不刪除它
public Item peek(){}
}

實現

2.1 數組實現(自動擴容)

時間復雜度分析:根據均攤復雜度的定義,可以得數組實現(自動擴容)符合大多數情況是O(1)級別復雜度,個別情況是O(n)級別復雜度,比如自動擴容時,會進行完整數據的拷貝。
空間復雜度分析:在入棧和出棧的過程中,只需要一兩個臨時變量存儲空間,所以O(1)級別。我們說空間復雜度的時候,是指除了原本的數據存儲空間外,算法運行還需要額外的存儲空間。

// 基于數組實現的順序棧 public class ArrayStack {private String[] items; // 數組private int count; // 棧中元素個數private int n; //棧的大小// 初始化數組,申請一個大小為n的數組空間public ArrayStack(int n) {this.items = new String[n];this.n = n;this.count = 0;}// 入棧操作public boolean push(String item) {// 數組空間不夠了,直接返回false,入棧失敗。if (count == n) return false;// 將item放到下標為count的位置,并且count加一items[count] = item;++count;return true;}// 出棧操作public String pop() {// 棧為空,則直接返回nullif (count == 0) return null;// 返回下標為count-1的數組元素,并且棧中元素個數count減一String tmp = items[count-1];--count;return tmp;} }

2.2 鏈表實現

時間復雜度分析:壓棧和彈棧的時間復雜度均為O(1)級別,因為只需更改單個節點的索引即可。
空間復雜度分析:在入棧和出棧的過程中,只需要一兩個臨時變量存儲空間,所以O(1)級別。我們說空間復雜度的時候,是指除了原本的數據存儲空間外,算法運行還需要額外的存儲空間。
實現代碼:(見另一條留言)

public class StackOfLinked<Item> implements Iterable<Item> { //定義一個內部類,就可以直接使用類型參數 private class Node{ Item item; Node next; } private Node first; private int N; //構造器 public StackOfLinked(){} //添加 public void push(Item item){ Node oldfirst = first; first = new Node(); first.item = item; first.next = oldfirst; N++; } //刪除 public Item pop(){ Item item = first.item; first = first.next; N--; return item; } //是否為空 public boolean isEmpty(){ return N == 0; } //元素數量 public int size(){ return N; } //返回棧中最近添加的元素而不刪除它 public Item peek(){ return first.item; } @Override public Iterator<Item> iterator() { return new LinkedIterator(); } //內部類:迭代器 class LinkedIterator implements Iterator{ int i = N; Node t = first; @Override public boolean hasNext() { return i > 0; } @Override public Item next() { Item item = (Item) t.item; t = t.next; i--; return item; } } }

三、棧的應用

1.棧在函數調用中的應用

操作系統給每個線程分配了一塊獨立的內存空間,這塊內存被組織成“棧”這種結構,用來存儲函數調用時的臨時變量。每進入一個函數,就會將其中的臨時變量作為棧幀入棧,當被調用函數執行完成,返回之后,將這個函數對應的棧幀出棧。

2.棧在表達式求值中的應用(比如:34+13*9+44-12/3)

利用兩個棧,其中一個用來保存操作數,另一個用來保存運算符。我們從左向右遍歷表達式,當遇到數字,我們就直接壓入操作數棧;當遇到運算符,就與運算符棧的棧頂元素進行比較,若比運算符棧頂元素優先級高,就將當前運算符壓入棧,若比運算符棧頂元素的優先級低或者相同,從運算符棧中取出棧頂運算符,從操作數棧頂取出2個操作數,然后進行計算,把計算完的結果壓入操作數棧,繼續比較。

3.棧在括號匹配中的應用(比如:{}{()})

用棧保存為匹配的左括號,從左到右一次掃描字符串,當掃描到左括號時,則將其壓入棧中;當掃描到右括號時,從棧頂取出一個左括號,如果能匹配上,則繼續掃描剩下的字符串。如果掃描過程中,遇到不能配對的右括號,或者棧中沒有數據,則說明為非法格式。
當所有的括號都掃描完成之后,如果棧為空,則說明字符串為合法格式;否則,說明未匹配的左括號為非法格式。

4.如何實現瀏覽器的前進后退功能?

我們使用兩個棧X和Y,我們把首次瀏覽的頁面依次壓如棧X,當點擊后退按鈕時,再依次從棧X中出棧,并將出棧的數據一次放入Y棧。當點擊前進按鈕時,我們依次從棧Y中取出數據,放入棧X中。當棧X中沒有數據時,說明沒有頁面可以繼續后退瀏覽了。當Y棧沒有數據,那就說明沒有頁面可以點擊前進瀏覽了。

JVM 內存管理中有個“堆棧”的概念。棧內存用來存儲局部變量和方法調用,堆內存用來存儲 Java 中的對象。那 JVM 里面的“棧”跟數據結構中的“棧”是不是一回事呢?

內存中的堆棧和數據結構堆棧不是一個概念,可以說內存中的堆棧是真實存在的物理區,數據結構中的堆棧是抽象的數據存儲結構。
內存空間在邏輯上分為三部分:代碼區、靜態數據區和動態數據區,動態數據區又分為棧區和堆區。

  • 代碼區:存儲方法體的二進制代碼。高級調度(作業調度)、中級調度(內存調度)、低級調度(進程調度)控制代碼區執行代碼的切換。
  • 靜態數據區:存儲全局變量、靜態變量、常量,常量包括final修飾的常量和String常量。系統自動分配和回收。
  • 棧區:存儲運行方法的形參、局部變量、返回值。由系統自動分配和回收。
  • 堆區:new一個對象的引用或地址存儲在棧區,指向該對象存儲在堆區中的真實數據。

leetcode上關于棧的題目:20,155,232,844,224,682,49

隊列

一、什么是隊列?

1.先進者先出,這就是典型的“隊列”結構。
2.支持兩個操作:入隊enqueue(),放一個數據到隊尾;出隊dequeue(),從隊頭取一個元素。
3.所以,和棧一樣,隊列也是一種操作受限的線性表。

二、如何實現隊列?

1.隊列API

public interface Queue {
public void enqueue(T item); //入隊
public T dequeue(); //出隊
public int size(); //統計元素數量
public boolean isNull(); //是否為空
}

2.實現

** 2.1 數組**

// 用數組實現的隊列 public class ArrayQueue {// 數組:items,數組大小:nprivate String[] items;private int n = 0;// head表示隊頭下標,tail表示隊尾下標private int head = 0;private int tail = 0;// 申請一個大小為capacity的數組public ArrayQueue(int capacity) {items = new String[capacity];n = capacity;}// 入隊public boolean enqueue(String item) {// 如果tail == n 表示隊列已經滿了if (tail == n) return false;items[tail] = item;++tail;return true;}// 出隊public String dequeue() {// 如果head == tail 表示隊列為空if (head == tail) return null;// 為了讓其他語言的同學看的更加明確,把--操作放到單獨一行來寫了String ret = items[head];++head;return ret;}// 入隊操作,將item放入隊尾,如圖public boolean enqueue(String item) {// tail == n表示隊列末尾沒有空間了if (tail == n) {// tail ==n && head==0,表示整個隊列都占滿了if (head == 0) return false;// 數據搬移for (int i = head; i < tail; ++i) {items[i-head] = items[i];}// 搬移完之后重新更新head和tailtail -= head;head = 0;}items[tail] = item;++tail;return true;} }


2.2 循環鏈表思想
關鍵隊空條件 head == tail
隊滿判斷條件 (tail+1)%n=head

public class CircularQueue {// 數組:items,數組大小:nprivate String[] items;private int n = 0;// head表示隊頭下標,tail表示隊尾下標private int head = 0;private int tail = 0;// 申請一個大小為capacity的數組public CircularQueue(int capacity) {items = new String[capacity];n = capacity;}// 入隊public boolean enqueue(String item) {// 隊列滿了if ((tail + 1) % n == head) return false;items[tail] = item;tail = (tail + 1) % n;return true;}// 出隊public String dequeue() {// 如果head == tail 表示隊列為空if (head == tail) return null;String ret = items[head];head = (head + 1) % n;return ret;} }

2.3 鏈表實現

public class LinkedQueue { //定義一個節點類 private class Node{ String value; Node next; } //記錄隊列元素個數 private int size = 0; //head指向隊頭結點,tail指向隊尾節點 private Node head; private Node tail; //申請一個隊列 public LinkedQueue(){} //入隊 public boolean enqueue(String item){ Node newNode = new Node(); newNode.value = item; if (size == 0) head = newNode; else tail.next = newNode; tail = newNode; size++; return true; } //出隊 public String dequeue(){ String res = null; if(size == 0) return res; if(size == 1) tail = null; res = head.value; head = head.next; size--; return res; }

三、隊列有哪些常見的應用?

1.阻塞隊列

1)在隊列的基礎上增加阻塞操作,就成了阻塞隊列。
2)阻塞隊列就是在隊列為空的時候,從隊頭取數據會被阻塞,因為此時還沒有數據可取,直到隊列中有了數據才能返回;如果隊列已經滿了,那么插入數據的操作就會被阻塞,直到隊列中有空閑位置后再插入數據,然后在返回。
3)從上面的定義可以看出這就是一個“生產者-消費者模型”。這種基于阻塞隊列實現的“生產者-消費者模型”可以有效地協調生產和消費的速度。當“生產者”生產數據的速度過快,“消費者”來不及消費時,存儲數據的隊列很快就會滿了,這時生產者就阻塞等待,直到“消費者”消費了數據,“生產者”才會被喚醒繼續生產。不僅如此,基于阻塞隊列,我們還可以通過協調“生產者”和“消費者”的個數,來提高數據處理效率,比如配置幾個消費者,來應對一個生產者。

2.并發隊列

1)在多線程的情況下,會有多個線程同時操作隊列,這時就會存在線程安全問題。能夠有效解決線程安全問題的隊列就稱為并發隊列。
2)并發隊列簡單的實現就是在enqueue()、dequeue()方法上加鎖,但是鎖粒度大并發度會比較低,同一時刻僅允許一個存或取操作。
3)實際上,基于數組的循環隊列利用CAS原子操作,可以實現非常高效的并發隊列。這也是循環隊列比鏈式隊列應用更加廣泛的原因。
3.線程池資源枯竭是的處理
在資源有限的場景,當沒有空閑資源時,基本上都可以通過“隊列”這種數據結構來實現請求排隊。

[劍指offer][JAVA]面試題第[09]題[用兩個棧實現隊列][LinkedList]

主要整理參考作者:姜威
筆記整理來源: 王爭 數據結構與算法之美

總結

以上是生活随笔為你收集整理的【数据结构与算法】栈与队列的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。