一、鏈表
1.1 定義
鏈表是一種常見(jiàn)的數(shù)據(jù)結(jié)構(gòu)由一系列節(jié)點(diǎn)按順序連接而成,一般每個(gè)節(jié)點(diǎn)包含一個(gè)數(shù)據(jù)元素和一個(gè)指向下一個(gè)節(jié)點(diǎn)的引用。
鏈表有多種類型:
- 單向鏈表:每個(gè)節(jié)點(diǎn)只有一個(gè)指向下一個(gè)節(jié)點(diǎn)的引用
- 雙向鏈表:每個(gè)節(jié)點(diǎn)有一個(gè)指向前一個(gè)節(jié)點(diǎn)和一個(gè)指向后一個(gè)節(jié)點(diǎn)的引用
- 循環(huán)鏈表:尾節(jié)點(diǎn)指向頭節(jié)點(diǎn)形成循環(huán)
- 雙向循環(huán)鏈表:既是雙向鏈表又是循環(huán)鏈表
1.2 實(shí)現(xiàn)
1.2.1 單向鏈表
單向鏈表是指每個(gè)節(jié)點(diǎn)包含一個(gè)指向下一個(gè)節(jié)點(diǎn)的指針,最后一個(gè)節(jié)點(diǎn)的指針為NULL。
以下是單向鏈表的基本結(jié)構(gòu):
struct ListNode
{
int val;
ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};
1.2.2 雙向鏈表
雙向鏈表是在單向鏈表的基礎(chǔ)上,每個(gè)節(jié)點(diǎn)還包含一個(gè)指向上一個(gè)節(jié)點(diǎn)的指針。
以下是雙向鏈表的基本結(jié)構(gòu):
struct ListNode
{
int val;
ListNode *prev;
ListNode *next;
ListNode(int x) : val(x), prev(NULL), next(NULL) {}
};
1.2.3 常見(jiàn)操作(反轉(zhuǎn) 合并 查找)
/* 鏈表反轉(zhuǎn) */
Node* reverseLinkedList(Node* head) {
Node* newHead = nullptr;
while(head != nullptr) {
Node* tmp = head->next;
head->next = newHead;
newHead = head;
head = tmp;
}
return newHead;
}
/* 鏈表合并 */
Node* mergeLinkedList(Node* head1, Node* head2) {
Node* dummy = new Node;
Node* cur = dummy;
while(head1 != nullptr && head2 != nullptr) {
if(head1->data < head2->data) {
cur->next = head1;
head1 = head1->next;
} else {
cur->next = head2;
head2 = head2->next;
}
cur = cur->next;
}
cur->next = (head1 != nullptr ? head1 : head2);
return dummy->next;
}
/* 鏈表查找 */
Node* findNode(Node* head, int data) {
Node* cur = head;
while(cur != nullptr && cur->data != data) {
cur = cur->next;
}
return cur;
}
1.3 應(yīng)用
鏈表在程序中有著多種應(yīng)用 例如:
- 實(shí)現(xiàn)LRU緩存淘汰算法
- 實(shí)現(xiàn)文件系統(tǒng)中的文件塊分配
- 鏈表作為數(shù)據(jù)流的緩沖區(qū)
使用雙向循環(huán)鏈表實(shí)現(xiàn)的LRU緩存示例:
class LRUCache
{
private:
struct CacheNode
{
int key;
int value;
CacheNode(int k, int v) : key(k), value(v) {}
};
int capacity;
list<CacheNode> cacheList;
unordered_map<int, list<CacheNode>::iterator> cacheMap;
public:
LRUCache(int capacity)
{
this->capacity = capacity;
}
int get(int key)
{
if (cacheMap.find(key) == cacheMap.end())
{
return -1;
}
// 把當(dāng)前節(jié)點(diǎn)移到鏈表頭部
cacheList.splice(cacheList.begin(), cacheList, cacheMap[key]);
return cacheMap[key]->value;
}
void put(int key, int value)
{
if (cacheMap.find(key) == cacheMap.end())
{
if (cacheList.size() == capacity)
{
// 刪除鏈表最后一個(gè)節(jié)點(diǎn)
int key = cacheList.back().key;
cacheMap.erase(key);
cacheList.pop_back();
}
cacheList.push_front(CacheNode(key, value));
cacheMap[key] = cacheList.begin();
}
else
{
// 修改節(jié)點(diǎn)的值,并移到鏈表頭部
cacheMap[key]->value = value;
cacheList.splice(cacheList.begin(), cacheList, cacheMap[key]);
}
}
};
使用LRU緩存:
LRUCache cache = LRUCache(2);
cache.put(1, 1);
cache.put(2, 2);
cout << cache.get(1) << endl; // 輸出 1
cache.put(3, 3);
cout << cache.get(2) << endl; // 輸出 -1,因?yàn)殒I 2 已經(jīng)被刪除了
cache.put(4, 4);
cout << cache.get(1) << endl; // 輸出 -1,因?yàn)殒I 1 已經(jīng)被刪除了
cout << cache.get(3) << endl; // 輸出 3
cout << cache.get(4) << endl; // 輸出 4
二、 棧-Stack 隊(duì)列-Queue
1.1 定義
- 棧(Stack):后進(jìn)先出(LIFO: Last In First Out)
- 隊(duì)列(Queue):先進(jìn)先出(FIFO: First In First Out)
棧和隊(duì)列是C++提供的兩種基本數(shù)據(jù)類型,定義如下:
// 棧的定義
template <class T> class Stack {
public:
Stack(); // 構(gòu)造函數(shù)
void push(const T&); // 將元素加入棧頂,返回 void
void pop(); // 移除棧頂?shù)脑兀祷?void
T& top(); // 返回棧頂元素的引用,不移除該元素
const T& top() const; // 返回常量棧頂元素的引用,不移除該元素
bool empty() const; // 判斷棧是否為空,返回 bool
int size() const; // 返回棧中元素的個(gè)數(shù)
};
// 隊(duì)列的定義
template<class T> class Queue {
public:
Queue(); // 構(gòu)造函數(shù)
void push(const T&); // 將元素加入隊(duì)尾,返回 void
void pop(); // 移除隊(duì)頭的元素,返回 void
T& front(); // 返回隊(duì)頭元素的引用,不移除該元素
const T& front() const; // 返回常量隊(duì)頭元素的引用,不移除該元素
bool empty() const; // 判斷隊(duì)是否為空,返回 bool
int size() const; // 返回隊(duì)中元素的個(gè)數(shù)
};
1.2 實(shí)現(xiàn)
棧和隊(duì)列的實(shí)現(xiàn)方法比較簡(jiǎn)單可以使用數(shù)組或鏈表來(lái)實(shí)現(xiàn)
1.2.1 數(shù)組實(shí)現(xiàn) 棧和隊(duì)列 示例:
// 數(shù)組實(shí)現(xiàn)的棧
template <class T> class Stack {
T* a;
int n; // 棧的大小
int t; // 棧頂元素的下標(biāo)
public:
Stack(int maxn) {
a = new T[maxn];
n = maxn;
t = -1;
}
bool empty() const {
return t == -1;
}
void push(const T& x) {
a[++t] = x;
}
void pop() {
--t;
}
T top() const {
return a[t];
}
};
// 數(shù)組實(shí)現(xiàn)的隊(duì)列
template<class T> class Queue {
T* a;
int n; // 隊(duì)列的大小
int head, tail; // 隊(duì)頭和隊(duì)尾的下標(biāo)
public:
Queue(int maxn) {
a = new T[maxn];
n = maxn;
head = 0;
tail = -1;
}
bool empty() const {
return head > tail;
}
void push(const T& x) {
a[++tail] = x;
}
void pop() {
++head;
}
T front() const {
return a[head];
}
};
1.2.3鏈表實(shí)現(xiàn) 棧和隊(duì)列 示例:
// 鏈表實(shí)現(xiàn)的棧
template <class T> class Stack {
// 定義鏈表節(jié)點(diǎn)
struct node {
T data;
node* next;
node(const T& d, node* n = nullptr): data(d), next(n) {}
};
node* t; // 棧頂節(jié)點(diǎn)
public:
Stack() : t(nullptr) {} // 默認(rèn)構(gòu)造函數(shù)
bool empty() const {
return t == nullptr;
}
void push(const T& x) {
t = new node(x, t);
}
void pop() {
node* tmp = t;
t = t->next;
delete tmp;
}
T& top() const {
return t->data;
}
};
// 鏈表實(shí)現(xiàn)的隊(duì)列
template <class T> class Queue {
// 定義鏈表節(jié)點(diǎn)
struct node {
T data;
node* next;
node(const T& d, node* n = nullptr) : data(d), next(n) {}
};
node* head; // 隊(duì)頭節(jié)點(diǎn)
node* tail; // 隊(duì)尾節(jié)點(diǎn)
public:
Queue() : head(nullptr), tail(nullptr) {} // 默認(rèn)構(gòu)造函數(shù)
bool empty() const {
return head == nullptr;
}
void push(const T& x) {
if (tail != nullptr) {
tail->next = new node(x);
tail = tail->next;
} else {
head = tail = new node(x);
}
}
void pop() {
node* tmp = head;
head = head->next;
delete tmp;
if (head == nullptr) {
tail = nullptr;
}
}
T& front() const {
return head->data;
}
};
1.3 應(yīng)用
棧和隊(duì)列是常用的數(shù)據(jù)結(jié)構(gòu)在程序中的應(yīng)用廣泛,例如:
- 表達(dá)式求值
- 函數(shù)調(diào)用
- 網(wǎng)絡(luò)請(qǐng)求處理
- 路徑搜索
如下使用棧來(lái)實(shí)現(xiàn)表達(dá)式求值示例:
// 使用棧來(lái)實(shí)現(xiàn)表達(dá)式求值
double calc(const string& s) {
Stack<double> stk;
for (int i = 0; i < s.size(); ++i) {
if (isdigit(s[i])) {
int j = i;
while (j < s.size() && (isdigit(s[j]) || s[j] == '.')) ++j;
stk.push(stod(s.substr(i, j - i)));
i = j - 1;
} else if (s[i] == '+') {
double b = stk.top(); stk.pop();
double a = stk.top(); stk.pop();
stk.push(a + b);
} else if (s[i] == '-') {
double b = stk.top(); stk.pop();
double a = stk.top(); stk.pop();
stk.push(a - b);
} else if (s[i] == '*') {
double b = stk.top(); stk.pop();
double a = stk.top(); stk.pop();
stk.push(a * b);
} else if (s[i] == '/') {
double b = stk.top(); stk.pop();
double a = stk.top(); stk.pop();
stk.push(a / b);
}
}
return stk.top();
}
三、樹(shù)和二叉樹(shù)
1.1 定義
1.1.1 樹(shù)的定義
樹(shù)是一種非線性的數(shù)據(jù)結(jié)構(gòu),它由n個(gè)節(jié)點(diǎn)組成的有限集合,其中每個(gè)節(jié)點(diǎn)是一個(gè)具有唯一標(biāo)識(shí)的對(duì)象,且節(jié)點(diǎn)之間存在著一些特定的關(guān)系。通常這些關(guān)系可以用父子關(guān)系來(lái)描述。每個(gè)節(jié)點(diǎn)可以有多個(gè)子節(jié)點(diǎn),但是一個(gè)節(jié)點(diǎn)只有一個(gè)父節(jié)點(diǎn),樹(shù)中有且僅有一個(gè)無(wú)父節(jié)點(diǎn)的節(jié)點(diǎn)這個(gè)節(jié)點(diǎn)被稱為根節(jié)點(diǎn)。
1.1.2 樹(shù)的特點(diǎn)
- 樹(shù)中的每個(gè)節(jié)點(diǎn)都有唯一的標(biāo)識(shí)
- 樹(shù)中每個(gè)節(jié)點(diǎn)至少有一個(gè)子節(jié)點(diǎn)
- 樹(shù)中節(jié)點(diǎn)之間的關(guān)系是“父子”關(guān)系
- 樹(shù)中有且僅有一個(gè)無(wú)父節(jié)點(diǎn)的節(jié)點(diǎn)這個(gè)節(jié)點(diǎn)被稱為根節(jié)點(diǎn)
- 樹(shù)中任意兩個(gè)節(jié)點(diǎn)之間都存在一條唯一的路徑
1.1.3 二叉樹(shù)的定義
二叉樹(shù)是一種特殊的樹(shù)結(jié)構(gòu),每個(gè)節(jié)點(diǎn)最多有兩個(gè)子節(jié)點(diǎn),它的左子節(jié)點(diǎn)和右子節(jié)點(diǎn)分別稱為左子樹(shù)和右子樹(shù)
1.1.4 二叉樹(shù)的特點(diǎn)
- 二叉樹(shù)中的每個(gè)節(jié)點(diǎn)都有唯一的標(biāo)識(shí)
- 一個(gè)節(jié)點(diǎn)最多只有兩個(gè)子節(jié)點(diǎn)
- 二叉樹(shù)中節(jié)點(diǎn)之間的關(guān)系是“父子”關(guān)系
- 如果二叉樹(shù)中任意一個(gè)節(jié)點(diǎn)(除了葉子節(jié)點(diǎn)),其左子樹(shù)不為空則左子樹(shù)中的所有節(jié)點(diǎn)的值都小于這個(gè)節(jié)點(diǎn)的值.如果其右子樹(shù)不為空則右子樹(shù)中所有節(jié)點(diǎn)的值都大于這個(gè)節(jié)點(diǎn)的值。
1.2 實(shí)現(xiàn)
1.2.1 實(shí)現(xiàn)方法
樹(shù)和二叉樹(shù)可以通過(guò)結(jié)構(gòu)體和指針實(shí)現(xiàn),使用鏈?zhǔn)浇Y(jié)構(gòu)來(lái)描述。節(jié)點(diǎn)包括一個(gè)數(shù)據(jù)域和兩個(gè)指針域分別指向左右子樹(shù)。
// 定義樹(shù)節(jié)點(diǎn)結(jié)構(gòu)體
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
1.2.2 常用算法
- 先序遍歷:遍歷順序?yàn)楦Y(jié)點(diǎn)、左子樹(shù)、右子樹(shù)。
- 中序遍歷:遍歷順序?yàn)樽笞訕?shù)、根結(jié)點(diǎn)、右子樹(shù)。
- 后序遍歷:遍歷順序?yàn)樽笞訕?shù)、右子樹(shù)、根結(jié)點(diǎn)。
- 層序遍歷:按層遍歷二叉樹(shù)。
先序遍歷的代碼示例:
void preorder_traversal(TreeNode *node) {
if (node) {
cout << node->val << " ";
preorder_traversal(node->left);
preorder_traversal(node->right);
}
}
中序遍歷的代碼示例:
void inorder_traversal(TreeNode *node) {
if (node) {
inorder_traversal(node->left);
cout << node->val << " ";
inorder_traversal(node->right);
}
}
后序遍歷的代碼示例:
void postorder_traversal(TreeNode *node) {
if (node) {
postorder_traversal(node->left);
postorder_traversal(node->right);
cout << node->val << " ";
}
}
層序遍歷的代碼示例:
void level_order_traversal(TreeNode *node) {
queue<TreeNode *> q;
q.push(node);
while (!q.empty()) {
TreeNode *temp = q.front();
q.pop();
cout << temp->val << " ";
if (temp->left) {
q.push(temp->left);
}
if (temp->right) {
q.push(temp->right);
}
}
}
1.3 應(yīng)用
在程序中的應(yīng)用有:
- 堆排序算法
- 哈夫曼編碼
- 字典樹(shù)
- AVL樹(shù)(自平衡二叉搜索樹(shù))
- B樹(shù)和B+樹(shù)(多路搜索樹(shù))
四、 圖結(jié)構(gòu)
1.1 定義
1.1.1 圖的定義
圖是一種數(shù)據(jù)結(jié)構(gòu),它由節(jié)點(diǎn)/頂點(diǎn) (vertex)和邊(edge)組成。
節(jié)點(diǎn)表示圖中的元素而邊描述它們之間的關(guān)系
在圖中元素間的關(guān)系不再是父子關(guān)系而是一個(gè)相關(guān)的關(guān)系
// 定義圖結(jié)構(gòu)體
struct Graph {
int V; // 節(jié)點(diǎn)個(gè)數(shù)
vector<int> *adj; // 指向相鄰頂點(diǎn)列表的指針
Graph(int V) {
this->V = V;
adj = new vector<int>[V];
}
void addEdge(int v, int w) { // 添加一條邊
adj[v].push_back(w); // 無(wú)向圖,邊為雙向,v->w 和 w->v 都需加入 adj 中
adj[w].push_back(v);
}
};
1.1.2 圖的類型
在圖論中重點(diǎn)討論無(wú)向圖和有向圖,以及它們的變種:
- 無(wú)向圖:由節(jié)點(diǎn)和無(wú)向邊組成的圖結(jié)構(gòu),邊沒(méi)有方向,節(jié)點(diǎn)間的關(guān)系是雙向的
- 有向圖:由節(jié)點(diǎn)和有向邊組成的圖結(jié)構(gòu),邊有方向,節(jié)點(diǎn)間的關(guān)系是單向的
- 帶權(quán)圖:除了節(jié)點(diǎn)和邊之外還有一些權(quán)重信息
- 多重圖:允許多個(gè)節(jié)點(diǎn)之間有多條邊
// 定義帶權(quán)圖結(jié)構(gòu)體
struct WeightedGraph {
int V; // 節(jié)點(diǎn)個(gè)數(shù)
vector<pair<int, int>> *adj; // pair中的第一個(gè)值表示相鄰頂點(diǎn),第二個(gè)值表示權(quán)重
WeightedGraph(int V) {
this->V = V;
adj = new vector<pair<int, int>>[V];
}
void addEdge(int v, int w, int weight) { // 添加一條帶權(quán)邊
adj[v].push_back(make_pair(w, weight));
adj[w].push_back(make_pair(v, weight)); // 無(wú)向圖,邊為雙向,v->w 和 w->v 都需加入 adj 中
}
};
1.2 實(shí)現(xiàn)
1.2.1 實(shí)現(xiàn)方法
- 鄰接矩陣:使用一個(gè)二維數(shù)組表示節(jié)點(diǎn)與邊之間的關(guān)系可以表示帶權(quán)圖
- 鄰接表:使用一個(gè)數(shù)組+鏈表的方式表示節(jié)點(diǎn)與邊之間的關(guān)系可以表示帶權(quán)圖
// 用鄰接表實(shí)現(xiàn)的廣度優(yōu)先遍歷算法
void bfs(Graph graph, int start) {
bool *visited = new bool[graph.V]; // 標(biāo)記節(jié)點(diǎn)是否被訪問(wèn)
for (int i = 0; i < graph.V; ++i) {
visited[i] = false;
}
queue<int> q;
visited[start] = true; // 標(biāo)記起始節(jié)點(diǎn)被訪問(wèn)
q.push(start);
while (!q.empty()) {
int curr = q.front();
cout << curr << " "; // 輸出訪問(wèn)順序
q.pop();
for (auto i = graph.adj[curr].begin(); i != graph.adj[curr].end(); ++i) {
if (!visited[*i]) { // 如果該節(jié)點(diǎn)未被訪問(wèn)
visited[*i] = true; // 標(biāo)記該節(jié)點(diǎn)被訪問(wèn)
q.push(*i); // 將該節(jié)點(diǎn)加入隊(duì)列中,等待遍歷
}
}
}
}
1.2.2 常用算法
- 深度優(yōu)先遍歷(DFS):以深度為優(yōu)先級(jí),沿著路徑遍歷,可以通過(guò)遞歸或棧實(shí)現(xiàn)
- 廣度優(yōu)先遍歷(BFS):以廣度為優(yōu)先級(jí),按照“就近原則”,先遍歷與起點(diǎn)直接相鄰的節(jié)點(diǎn),再遍歷距離起點(diǎn)稍遠(yuǎn)的節(jié)點(diǎn),可以使用隊(duì)列實(shí)現(xiàn)
- 最短路徑算法:解決兩個(gè)節(jié)點(diǎn)之間的最短路徑問(wèn)題
- 最小生成樹(shù)算法:解決連通圖中,權(quán)值最小的生成樹(shù)問(wèn)題
// 用鄰接矩陣實(shí)現(xiàn)的深度優(yōu)先遍歷算法
void dfs(int v, bool visited[], int **graph, int size) {
visited[v] = true; // 標(biāo)記該節(jié)點(diǎn)被訪問(wèn)
cout << v << " "; // 輸出訪問(wèn)順序
for (int i = 0; i < size; ++i) {
if (graph[v][i] && !visited[i]) { // 如果該節(jié)點(diǎn)未被訪問(wèn)且與 v 有連接
dfs(i, visited, graph, size); // 遞歸遍歷該節(jié)點(diǎn)
}
}
}
1.3 應(yīng)用
圖作為一種重要的數(shù)據(jù)結(jié)構(gòu)在程序中的應(yīng)用有:
- 最短路徑問(wèn)題:GPS 導(dǎo)航
- 網(wǎng)絡(luò)問(wèn)題:路由算法
- 社交網(wǎng)絡(luò):朋友推薦
- 游戲策略:國(guó)際象棋
- 電路設(shè)計(jì):電路布線
五、小結(jié)回顧
鏈表是一種常用的線性數(shù)據(jù)結(jié)構(gòu),它可以動(dòng)態(tài)的添加或刪除元素并且沒(méi)有固定大小
鏈表是由節(jié)點(diǎn)組成的每個(gè)節(jié)點(diǎn)包含一個(gè)數(shù)據(jù)元素和一個(gè)指向下一個(gè)節(jié)點(diǎn)的指針
棧是一種后進(jìn)先出的數(shù)據(jù)結(jié)構(gòu)所有元素都在棧頂,可以理解為一種特殊的列表。
棧的基本操作包括入棧和出棧有時(shí)會(huì)使用壓入和彈出這樣的類比來(lái)描述這些操作。
隊(duì)列是一種先進(jìn)先出的數(shù)據(jù)結(jié)構(gòu)所有元素被放置在隊(duì)列的末尾并從隊(duì)列的開(kāi)頭移除。
隊(duì)列是一種有限制的列表它僅允許在列表的前面進(jìn)行插入而僅允許在列表的后面進(jìn)行刪除
樹(shù)是一種非線性數(shù)據(jù)結(jié)構(gòu)由由節(jié)點(diǎn)組成,并且節(jié)點(diǎn)之間有特定的關(guān)系(如父節(jié)點(diǎn)、子節(jié)點(diǎn)等)
每個(gè)節(jié)點(diǎn)可以包含一個(gè)數(shù)據(jù)元素,還可以連接到其他節(jié)點(diǎn)。樹(shù)被廣泛應(yīng)用于各種算法和數(shù)據(jù)結(jié)構(gòu)中文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-442206.html
圖是一種非線性的數(shù)據(jù)結(jié)構(gòu)由一個(gè)或多個(gè)節(jié)點(diǎn)組成并且節(jié)點(diǎn)之間有關(guān)聯(lián)
圖是由邊和節(jié)點(diǎn)組成的每個(gè)邊連接兩個(gè)節(jié)點(diǎn)
圖在計(jì)算機(jī)科學(xué)和算法中被廣泛應(yīng)用,例如網(wǎng)絡(luò)路由、圖像處理、算法設(shè)計(jì)等。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-442206.html
到了這里,關(guān)于C++數(shù)據(jù)結(jié)構(gòu)與算法詳解:鏈表、棧、隊(duì)列、樹(shù)、二叉樹(shù)和圖結(jié)構(gòu)的實(shí)現(xiàn)與應(yīng)用的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!