09--解密棧與隊列:數據結構核心原理

1. 棧

1.1. 棧的簡介

是一種 特殊的線性表,具有數據 先進后出 特點。

注意:

  1. stack本身 不支持迭代器操作
    主要原因是因為stack不支持數據的隨機訪問,必須保證數據先進后出的特點。
  2. stack在CPP庫中實現為一種 容器適配器
    所謂容器適配器,是為了適應不同的數據存儲而修改底層的數據結構從而達到優化效率的目的。
    參考:C++ STL容器適配器(詳解)

1.2. 棧為什么沒有提供迭代器???

棧為什么沒有提供迭代器???

我們看CPP::stack的文檔,發現stack并沒有提供迭代器,這是因為stack要保證其自身特性是先進后出,后進先出的特性,如果提供了迭代器,就能夠打破這一規則,棧也不再是棧。

1.3. 棧簡化模擬實現

棧簡化模擬實現

C版簡化模擬棧:【數據結構】棧
CPP版簡化模擬棧:

#pragma once
#include<vector>
#include<iostream>using namespace std;
namespace szg
{template<class T, class Container = vector<T>>class stack{private:Container _st;public:void push_back(const T& num){_st.push_back(num);}void pop_back(){_st.pop_back();}bool empty(){return _st.empty();}size_t size(){return _st.size();}const T& top(){return _st.back();}};
}

實際上,stack在庫中給的容器缺省值是deque,是一個 順序表與鏈表的結合體
deque參考文檔:stl_deque
deque底層邏輯簡介:【CPP】雙端隊列簡介(deque)

1.4. 適配器

適配器

在上面我們模擬實現的棧中,用到了Container,這個適配器。

什么是適配器?所謂適配器就是服務于我們所寫的類能夠支持其快速底層轉換的一種方式。

適配器模式主要應用于,當接口里定義的方法無法滿足客戶的需求,或者說接口里定義的方法的名稱或者方法界面與客戶需求有沖突的情況。

兩類模式:

對象適配器模式 - 在這種適配器模式中,適配器容納一個它我包裹的類的實例。在這種情況下,適配器調用被包裹對象的物理實體。

類適配器模式 - 這種適配器模式下,適配器繼承自已實現的類(一般多重繼承)。

適配器不具備數據速率轉換功能。

說到這里,我來簡單介紹一種新的容器,專門用來做適配器的容器——deque

2. deque雙端隊列

deque是一個融合了listvector相關特性的“混合容器”,既有list的快速插入刪除特性,也有vector[]隨機訪問功能,算是“文武雙全”的容器。

應用:作為容器適配器使用。

2.1. deque的特性

容器

vector

list

deque

優點

支持下標隨機訪問,[]效率高

任意位置插入刪除效率高

頭插、尾插效率高

缺點

頭部/中間插入刪除效率低,擴容消耗大

不支持[]隨機訪問

中間插入刪除效率一般且[]效率不夠極致

2.2. deque的內部構造

deque的內部控制是依靠迭代器實現的。

  • cur是指向當前的訪問元素
  • first是指向當前buff的開始元素
  • end是指向當前buff的末尾元素的下一個地址
  • node是指向當前buff在中控數組中存放的位置

2.3. deque的插入刪除遍歷邏輯

deque的插入和刪除:

deque的頭插尾插效率是挺高的。這是因為尾插一個元素后,迭代器會看看中控數組最后一個buff是否還有空間,如果有則尾插到最后一個buff,如果沒有就新開一個buff插入。頭插一個元素,他會現在中控數組的頭部開一個buff,因為默認是從中控數組中間開始新增的,所以可以支持常數時間開空間,之后同尾插同理。

中間插入插入元素處理比較麻煩

  • 如果中間插入元素后面所有元素都往后挪動一位,效率比較低

  • 如果中間插入元素改變buff的大小,那么上面[]訪問規則就不適用,會很麻煩。

deque的元素[]訪問計算規則,且[]訪問效率一般

一般情況下,buff每個都是相同大小并且沒有頭插新元素時候,下標訪問可以采用

  • 先找是第幾個buff,n/buff.size()
  • 在確定是這個buff中的第幾個元素,n%buff.size()

但是如果有頭插元素,首先應該減去第一個buff元素的個數,然后在進行上面步驟。

  • n-=buff1.size()
void test_op1()
{srand(time(0));const int N = 1000000;deque<int> dq;vector<int> v;for (int i = 0; i < N; ++i){auto e = rand() + i;v.push_back(e);dq.push_back(e);}int begin1 = clock();sort(v.begin(), v.end());int end1 = clock();int begin2 = clock();sort(dq.begin(), dq.end());int end2 = clock();printf("vector:%d\n", end1 - begin1);printf("deque:%d\n", end2 - begin2);
}
//vector:259
//deque:1263void test_op2()
{srand(time(0));const int N = 1000000;deque<int> dq1;deque<int> dq2;for (int i = 0; i < N; ++i){auto e = rand() + i;dq1.push_back(e);dq2.push_back(e);}int begin1 = clock();sort(dq1.begin(), dq1.end());int end1 = clock();int begin2 = clock();// 拷貝到vectorvector<int> v(dq2.begin(), dq2.end());sort(v.begin(), v.end());dq2.assign(v.begin(), v.end());int end2 = clock();printf("deque sort:%d\n", end1 - begin1);printf("deque copy vector sort, copy back deque:%d\n", end2 - begin2);
}
//deque sort : 1345
//deque copy vector sort, copy back deque : 358

2.4. deque作為stack/queue適配器的優先性?

為什么CPP庫中選擇deque作為stack/queue的適配器呢?

因為stackqueue都只會用到頭插尾插頭刪尾刪,恰好deque頭尾插刪效率都很好。也可以說,deque就是專門為stack/queue適配專門設計的一個容器。

3. 隊列queue

queue是隊列的含義,其特點是保證了數據先進先出,后進后出的特點,底層可以用vector、list或deque進行適配。

3.1. 隊列的簡單模擬實現

#define _CRT_SECURE_NO_WARNINGS 1
#include<deque>namespace szg
{template<class T, class Container = std::deque<T>>class queue{private:Container _con;public:size_t size(){return _con.size();}bool empty(){return _con.empty();}T& front(){return _con.front();}T& back(){return _con.back();}void push(const T& x){_con.push_back(x);}void pop(){_con.pop_front();}};
}
#define _CRT_SECURE_NO_WARNINGS 1
#include"Queue.h"
#include<iostream>int main()
{szg::queue<int> q;for (int i = 0; i < 100; i++){q.push(i);}while (!q.empty()){std::cout << q.front() << " ";q.pop();}std::cout << std::endl;//0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99return 0;
}

3.2. 隊列與棧的異同?

隊列與棧的差異重點在于相同序列進入容器,出數據會有所不同。

容器

數據變化

棧的出數據序列會變化,這取決于棧的push,pop順序

隊列

隊列的出數據序列不會變化,是恒定的

4. priority_queue優先級隊列 -> 堆

4.1. 簡單介紹

優先級隊列不是隊列,是一種容器適配器,底層是大堆。

在優先級隊列提供了一些接口允許公開調用:

4.2. 簡單使用

// 優先級隊列,默認底層是大堆。
priority_queue<int> ps;
ps.push(2);
ps.push(3);
ps.push(4);
ps.push(1);while (!ps.empty())
{cout << ps.top() << " ";ps.pop();
}//4 3 2 1

我們發現,優先級隊列默認是大堆

大堆小堆 升序降序

  1. 優先級隊列默認是大堆
  • 小于是大堆
  • 大于是小堆
  1. CPP中STL::sort()
  • 小于是升序
  • 大于是降序

這里區分兩個概念:取出結果是有序 真正排序 的區別。

在上面優先級隊列中,我們發現while+top取出的數據是有序的,這是因為我們每次取得都是堆頂元素。至于怎么弄的,可以參考C數據結構鐘堆刪除數據時候得操作,首尾交換,然后size--,我覺得應該是相同的操作。

顯然上面不是真正的有序,因為優先級隊列中是堆。我們可以用sort這個算法函數來進行區分。

我們發現兩個greater一個帶括號一個不帶,這是什么情況呢?

priority_queue<int, vector<int>, greater<int>> pq;
sort(v.begin(), v.end(), greater<int>());

模板參數與函數參數的需要

要注意優先級隊列是一種模板,需要的是類型進行實例化,而sort是函數模板實例化出來的一種函數,需要迭代器區間和具體的比較仿函數對象,而不是僅僅一個仿函數類型就行。

4.3. 仿函數

既然上面用到了仿函數,這里來介紹一下。

仿函數:也稱函數對象,仿函數是一種特殊的對象!他的對象可以像函數一樣去使用。

下面進行舉例:

//仿函數類
struct Less
{
public:bool operator()(const int& x, const int& y){return x < y;}
};void Test()
{Less lessfunc;cout << lessfunc(5,6) << endl; // 結果:1
}

這里需要注意哈,上面的數字5和6作為參數傳遞給operator(),如果要用引用來接收,必須前面加上const,因為這是引用常量值

//仿函數類
struct Less
{
public:bool operator()(const int& x, const int& y){return x < y;}
};void Test()
{Less lessfunc;cout << lessfunc(5,6) << endl;//1vector<int> v = { 1,3,4,2 };vector<int>::iterator it = v.begin();while (it != v.end()){cout << *it << " ";it++;}//1 3 4 2cout << endl;sort(v.begin(), v.end(), lessfunc);it = v.begin();while (it != v.end()){cout << *it << " ";it++;}//1 2 3 4
}

然后上面的仿函數類可以加上模板的語法,就跟我們用的greater<int>差不多了。

//仿函數類
template<typename T>
struct Less
{
public:bool operator()(const T& x, const T& y){return x < y;}
};void Test()
{vector<int> v = { 1,3,4,2 };vector<int>::iterator it = v.begin();while (it != v.end()){cout << *it << " ";it++;}cout << endl;sort(v.begin(), v.end(), Less<int>());it = v.begin();while (it != v.end()){cout << *it << " ";it++;}
}

4.4. 優先級隊列模擬實現

#pragma once
#include<vector>
#include<iostream>
using namespace std;template<typename T>
struct Less
{
public:bool operator()(const T& x, const T& y){return x < y;}
};template<typename T>
struct Greater
{
public:bool operator()(const T& x, const T& y){return x > y;}
};template<class T, class Container = vector<T>, class Compare = Greater<T>>
class periority_queue
{
private:Container _con;public:void adjust_up(int child){Compare com;int parent = (child - 1) / 2;while (child > 0){if (com(_con[child], _con[parent])){swap(_con[child], _con[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}}void push(const T& x){_con.push_back(x);adjust_up(_con.size() - 1);}void adjust_down(int parent){Compare com;int child = parent * 2 + 1;while (child < _con.size()){if (child + 1 < _con.size() && com(_con[child + 1], _con[child])){child = child + 1;}if (com(_con[child], _con[parent])){swap(_con[child], _con[parent]);parent = child;child = parent * 2 + 1;}else{break;}}}void pop(){swap(_con[0], _con[_con.size() - 1]);_con.pop_back();adjust_down(0);}size_t size(){return _con.size();}bool empty(){return _con.empty();}const T& top(){return _con[0];}
};
// 指針模板
template<class T>
struct GreaterPDate
{bool operator()(const T& d1, const T& d2){return *d1 > *d2;}
};void test_priority_queue2()
{priority_queue<Date*, vector<Date*>, GreaterPDate<Date*>> pqptr;Date d1(2024, 4, 14);Date d2(2024, 4, 11);Date d3(2024, 5, 15);pqptr.push(&d1);pqptr.push(&d2);pqptr.push(&d3);while (!pqptr.empty()){cout << *(pqptr.top()) << " ";pqptr.pop();}cout << endl;
}

5. 反向迭代器

反向迭代器,顧名思義,反向迭代器的操作與正向迭代器恰好相反(方向)。

5.1. 簡單介紹

下面我用庫函數來進行一個簡單演示。

std::list<int> l = { 1,2,3,4,5,6 };
std::list<int>::reverse_iterator rit = l.rbegin();
while (rit != l.rend())
{std::cout << *rit << " ";++rit;
}//6 5 4 3 2 1
std::cout << std::endl;

5.2. 反向迭代器的設計思路

  1. 思路1:我們可以像前面實現const迭代器一樣寫一個類,顯然,這樣我們即使寫模板也需要針對不同的容器進行寫不同的反向迭代器。
  2. 思路2:封裝iterator,然后重載其部分運算符。

下面來重點介紹第二種思路的實現方式:

這樣的好處是,我們只需要設計一個反向迭代器類,就可以根據不同的正向迭代器自由變換其反向迭代器。

5.3. 反向迭代器的模擬實現

#define _CRT_SECURE_NO_WARNINGS 1namespace szg
{template<class Tterator, class Ref, class Ptr>class ReverseIterator{private:Tterator _it;public:typedef ReverseIterator<Tterator, Ref, Ptr> Self;//構造函數ReverseIterator(Tterator it):_it(it){}//解引用Ref operator*(){Tterator temp = _it;temp--;return *temp;}//->函數Ptr operator->(){return &(this->operator*());}//前置++Self& operator++(){--_it;return *this;}//前置--Self& operator--(){++_it;return *this;}//!=函數重載bool operator!=(const Self& s){return _it != s._it;}};
}
#include"List.h"
#include<list>void test()
{szg::list<int> l = { 1, 2, 3, 4, 5, 6 };/*l.push_back(1);l.push_back(2);l.push_back(3);l.push_back(4);l.push_back(5);l.push_back(6);*/szg::list<int>::reverse_iterator rit = l.rbegin();while (rit != l.rend()){std::cout << *rit << " ";++rit;}//6 5 4 3 2 1std::cout << std::endl;
}int main()
{//std::list<int> l = { 1,2,3,4,5,6 };//std::list<int>::reverse_iterator rit = l.rbegin();//while (rit != l.rend())//{//	std::cout << *rit << " ";//	++rit;//}//6 5 4 3 2 1//std::cout << std::endl;test();return 0;
}

5.4. rbegin、rend的解釋

在庫中,使用的是第一種方式設計的rbegin和rend,為什么呢?沒啥意義,感覺單純與begin,end對稱一些。在解引用的時候,一直是解引用的下一個值而已。

5.5. 迭代器的分類

迭代器性質分類

單向

forward_list

++

雙向

list

++/--

隨機

vector/deque

++/--/+/-

迭代器功能分裂

正向

普通一般的迭代器

反向

反方向的正向迭代器

const

對指向內容只能讀不能寫

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/95402.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/95402.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/95402.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

打造專屬 React 腳手架:從 0 到 1 開發 CLI 工具

前言: 在前端開發中&#xff0c;重復搭建項目環境是個低效的事兒。要是團隊技術棧固定&#xff08;比如 React AntD Zustand TS &#xff09;&#xff0c;每次從零開始配路由、狀態管理、UI 組件&#xff0c;既耗時又容易出錯。這時候&#xff0c;自定義 CLI 腳手架 就派上…

Python day43

浙大疏錦行 Python day43 import torch import numpy as np import pandas as pd import torchvision import torchvision.transforms as transforms import torch.nn as nn import torch.optim as optim import torch.nn.functional as F from torch.utils.data import Da…

python基于Hadoop的超市數據分析系統

前端開發框架:vue.js 數據庫 mysql 版本不限 后端語言框架支持&#xff1a; 1 java(SSM/springboot)-idea/eclipse 2.NodejsVue.js -vscode 3.python(flask/django)–pycharm/vscode 4.php(thinkphp/laravel)-hbuilderx 數據庫工具&#xff1a;Navicat/SQLyog等都可以 摘要&…

如何用 COLMAP 制作 Blender 格式的數據集

如何用 COLMAP 制作 Blender 格式的數據集并劃分出 transforms_train.json、transforms_val.json 和 transforms_test.json。 一、什么是 Blender 格式數據集? Blender 格式數據集是 Nerf 和 Nerfstudio 常用的輸入格式,其核心是包含了相機內外參的 JSON 文件,一般命名為:…

[GESP202309 六級] 2023年9月GESP C++六級上機題題解,附帶講解視頻!

本文為GESP 2023年9月 六級的上機題目詳細題解和講解視頻&#xff0c;覺得有幫助或者寫的不錯可以點個贊。 題目一講解視頻 GESP2023年9月六級上機題一題目二講解視頻 題目一:小羊買飲料 B3873 [GESP202309 六級] 小楊買飲料 - 洛谷 題目大意: 現在超市一共有n種飲料&#…

linux 操作ppt

目錄 方法1&#xff1a;用 libreoffice 打開PPT文件 播放腳本&#xff1a; 方法2&#xff1a;用 python-pptx 創建和編輯PPT 方法3&#xff1a;其他方法 在Linux中&#xff0c;可以使用Python通過python-pptx庫來創建和編輯PPT文件&#xff0c;但直接播放PPT文件需要借助其…

元數據管理與數據治理平臺:Apache Atlas 基本搜索 Basic Search

文中內容僅限技術學習與代碼實踐參考&#xff0c;市場存在不確定性&#xff0c;技術分析需謹慎驗證&#xff0c;不構成任何投資建議。 Apache Atlas 框架是一套可擴展的核心基礎治理服務&#xff0c;使企業能夠有效、高效地滿足 Hadoop 中的合規性要求&#xff0c;并支持與整個…

LangChain4J-(1)-Hello World

一、LangChain4J是什么&#xff1f; LangChain4J 是一個專為 Java 生態系統設計的開源框架&#xff0c;用于簡化與大語言模型&#xff08;LLM&#xff0c;如 OpenAI 的 GPT 系列、Google 的 Gemini、Anthropic 的 Claude 等&#xff09;的集成和交互。它借鑒了 Python 生態中 L…

HTTPS應用層協議-中間攻擊人

HTTPS應用層協議-中間攻擊人 ? Man-in-the-MiddleAttack&#xff0c;簡稱“MITM 攻擊” 確實&#xff0c;在方案 2/3/4 中&#xff0c;客戶端獲取到公鑰 S 之后&#xff0c;對客戶端形成的對稱秘鑰 X 用服務端給客戶端的公鑰 S 進行加密&#xff0c;中間人即使竊取到了數據&am…

利用 Makefile 高效啟動 VIVADO 軟件:深入解析與實踐

利用 Makefile 高效啟動 VIVADO 軟件&#xff1a;深入解析與實踐 系列文章目錄 1、VMware Workstation Pro安裝指南&#xff1a;詳細步驟與配置選項說明 2、VMware 下 Ubuntu 操作系統下載與安裝指南 3.基于 Ubuntu 的 Linux 系統中 Vivado 2020.1 下載安裝教程 文章目錄利用 …

[前端算法]排序算法

默認情況下&#xff0c;sort() 會將元素轉換為字符串&#xff0c;然后按照 Unicode 編碼的順序進行排序&#xff1a; const fruits [apple, banana, cherry, date]; fruits.sort(); console.log(fruits); // 輸出: ["apple", "banana", "cherry"…

C#標簽批量打印程序開發

C#標簽批量打印程序開發&#xff08;集成Bartender解決方案&#xff09;一、系統架構設計 1. 核心模塊劃分 public class LabelPrintingSystem {private IDataLoader _dataLoader; // 數據加載器private ITemplateEngine _templateEngine; // 模板引擎private IPrintControl…

ECC的原理、背景、工作機制和數學基礎

ECC的原理、背景、工作機制和數學基礎摘要&#xff1a;本文首先詳細介紹ECC&#xff08;Error-Correcting Code&#xff0c;糾錯碼&#xff09;的原理&#xff0c;包括背景、工作機制和數學基礎。然后&#xff0c;解釋ECC在SRAM&#xff08;Static Random-Access Memory&#x…

計算機網絡2-2:物理層下面的傳輸媒體

目錄 導引型傳輸媒體 同軸電纜 雙絞線 光纖 電力線 非導引型傳輸媒體 無線電波 微波 紅外線 可見光 無線電頻譜管理機構 導引型傳輸媒體 同軸電纜 雙絞線 光纖 光在光纖中傳播的基本原理 電力線 非導引型傳輸媒體 無線電波 微波 紅外線 可見光 LiFi(可見光通信) …

Dify 從入門到精通(第 32/100 篇):Dify 的日志分析與監控

Dify 從入門到精通&#xff08;第 32/100 篇&#xff09;&#xff1a;Dify 的日志分析與監控 Dify 入門到精通系列文章目錄 第一篇《Dify 究竟是什么&#xff1f;真能開啟低代碼 AI 應用開發的未來&#xff1f;》介紹了 Dify 的定位與優勢第二篇《Dify 的核心組件&#xff1a…

【IntelliJ IDEA】修改堆內存

idea卡頓&#xff0c;鼠標漂移修改idea文件打開 idea 安裝路徑&#xff0c;【bin】目錄下【idea64.exe.vmoptions】文件修改【-Xms】最小內存【-Xmx】最大內存-Xms2048m -Xmx9216midea更改內存設置工具欄幫助更改內存設置設置堆大小上限為 文件 設置的最大內存保存并重啟Leslie…

Docker與Docker Compose:容器世界的“單兵作戰”與“軍團指揮官”

在容器化技術的浪潮中&#xff0c;Docker和Docker Compose如同“雙子星”&#xff0c;一個專注于單兵作戰&#xff0c;一個擅長軍團指揮。它們看似相似&#xff0c;卻各司其職。對于開發者來說&#xff0c;理解它們的區別不僅能讓代碼部署事半功倍&#xff0c;更能避免踩坑。本…

進階向:Python編寫自動化郵件發送程序

Python編寫自動化郵件發送程序&#xff1a;從零開始詳解在數字化時代&#xff0c;自動化郵件發送功能已成為企業和個人提升工作效率的重要工具。據統計&#xff0c;全球每天發送的商業郵件超過30億封&#xff0c;其中約40%是通過自動化系統發送的。這種功能被廣泛應用于多種場景…

ChatGpt 5系列文章1——編碼與智能體

人工智能技術正在以驚人的速度發展&#xff0c;重新定義著開發人員的工作方式。2025年8月&#xff0c;OpenAI正式發布了面向開發人員的GPT-5 一、GPT-5的編碼能力突破 GPT-5在關鍵編碼基準測試中創造了行業新紀錄(SOTA)&#xff0c;在SWE-bench Verified測試中得分74.9%&…

力扣top100(day02-05)--二叉樹 02

102. 二叉樹的層序遍歷 /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val val; }* TreeNode(int val, TreeNode left, TreeNode right)…