文章目錄
- 1. 線性表的結構體定義
- 2. 順序表的基本操作
- 3. 單鏈表的基本操作
- 3.1)初始化單鏈表
- 3.2)尾插法建立單鏈表
- 3.3)頭插法建立單鏈表
- 3.4)合併遞增單鏈表
- 3.5)合併遞減單鏈表
- 3.6)查找元素並刪除
- 3.7)對於一個遞增單鏈表,刪除其重複的元素
- 3.8)刪除單鏈表中的最小值
- 3.9)單鏈表的原地逆置
- 3.10)將一個數據域爲整數的單鏈表分割爲兩個分別爲奇數和偶數的鏈表
- 3.11)逆序打印單鏈表中的節點
- 3.12)查找鏈表中倒數第k個節點
- 4. 雙鏈表的基本操作
- 5. 棧和隊列的結構體定義
- 6. 順序棧的基本操作
- 7. 鏈棧的基本操作
- 8. 順序隊列的基本操作
- 9. 鏈隊的基本操作
- 10. 串的結構體定義
- 11. 串的基本操作
- 12. 串的模式匹配
- 13. 二叉樹的結點定義
- 13. 二叉樹的遍歷算法
- 14. 圖的存儲結構
- 15. 圖的遍歷方式
- 16. 最小(代價)生成樹
- 17. 最短路徑算法
- 18. 拓撲排序
- 19. 排序算法
- 20. 折半查找法
- 21. 二叉排序樹
- <2020年考研,必須上岸>
1. 線性表的結構體定義
1.1)順序表的結構體定義
typedef struct Sqlist{
int data[maxSize];
int length;
}Sqlist;
1.2)考試中順序表定義簡寫法
int A[maxSize];
int n;
1.3)單鏈表的結構體定義
typedef struct LNode{
int data;
struct LNode *next;
}LNode;
1.4)雙鏈表結構體定義
typedef struct DLNode {
int data;
struct DLNode *prior;
struct DLNode *next;
}DLNode;
2. 順序表的基本操作
2.1)初始化順序表
void initList(Sqlist &L) {
L.length = 0;
}
2.2)求指定位置元素
int getElem(Sqlist L, int p, int &e){
if (p < 0 || p >= L.length) {
return 0;
}
e = L.data[p];
return 1;
}
2.3)插入數據元素
int insertElem(Sqlist &L, int p, int e) {
if (p < 0 || p > L.length || L.length == maxSize) {
return 0;
}
for (int i = L.length-1; i >= p; i--) {
L.data[i+1] = L.data[i];
}
L.data[p] = e;
++(L.length);
return 1;
}
2.4)按元素值查找
int findElem(Sqlist L, int e) {
for (int i = 0; i < L.length; i++) {
if (L.data[i] == e) {
return i;
}
}
return -1;
}
2.5)刪除數據元素
int deleteElem(Sqlist &L, int p, int &e) {
if (p < 0 || p >= L.length) {
return 0;
}
e = L.data[p];
for (int i = p; i < L.length-1; i++) {
L.data[i] = L.data[i+1];
}
--(L.length);
return 1;
2.6)順序表的元素逆置
void reverse(Sqlist &L) {
int i, j, temp;
for (i = 0, j = L.length - 1; i < j; ++i, --j) {
// 交換
temp = L.data[i];
L.data[i] = L.data[j];
L.data[j] = temp;
}
}
2.7)刪除下標爲i~j的數據元素
void deleteRange(Sqlist &L, int i, int j) {
assert(0 <= i && 0 <= j && i < L.length && j < L.length);
// 用j+1後面的元素去覆蓋往前數第j-i+1個元素
int delta = j - i + 1;
for (int k = j+1; k < L.length; ++k) {
L.data[k-delta] = L.data[k];
}
L.length -= delta;
}
2.8)Partition操作
void partition(Sqlist &L) {
assert(L.length != 0);
int p = L.data[0];
int i = 0, j = L.length-1;
while (i < j) {
while (i < j && L.data[j] > p) --j;
if (i < j) {
L.data[i] = L.data[j];
++i;
}
while (i < j && L.data[i] < p) ++i;
if (i < j) {
L.data[j] = L.data[i];
--j;
}
}
L.data[i] = p;
}
3. 單鏈表的基本操作
3.1)初始化單鏈表
void InitLinkList(LNode *list) {
assert(list != NULL);
list->next = NULL;
}
3.2)尾插法建立單鏈表
void createlistR(LNode *&list, int a[], int n) {
LNode *s, *r; // s用來指向新申請的節點,r始終指向list的終端節點
int i;
list = (LNode*)malloc(sizeof(LNode));
list->next = NULL;
r = list;
for (int i = 0; i < n; i++) {
s = (LNode*)malloc(sizeof(LNode));
s->data = a[i];
r->next = s; // 讓當前的終端節點指向新來的節點
r = r->next; // 更新r,讓r指向新的終端節點
}
r->next = NULL;
3.3)頭插法建立單鏈表
void createlistF(LNode *&list, int a[], int n) {
LNode *s; // s用來指向新申請的節點
list = (LNode*)malloc(sizeof(LNode));
list->next = NULL;
for (int i = 0; i < n; i++) {
s = (LNode*)malloc(sizeof(LNode));
s->data = a[i];
s->next = list->next;
list->next = s;
}
}
3.4)合併遞增單鏈表
void mergeR(LNode *A, LNode *B, LNode *&C) {
// 由於不能改變了A, B自己的內容,所以得設定p, q來進行操作
LNode *p = A->next;
LNode *q = B->next;
LNode *r; // r始終指向C的終端節點
C = A; // 將A的頭結點用來做C的頭結點
C->next = NULL;
free(B);
r = C; // 初試的時候C也是終端節點
while (p != NULL && q != NULL) {
if (p->data <= q->data) {
r->next = p; // 讓當前的終端節點指向新的終端節點
p = p->next; // p自然要往後挪一步
r = r->next; // 更新r的指向,讓r指向新的終端節點
} else {
r->next = q;
q = q->next;
r = r->next;
}
}
// p還有剩餘
if (p != NULL) {
r->next = p;
}
// q還有剩餘
if (q != NULL) {
r->next = q;
}
}
3.5)合併遞減單鏈表
void mergeF(LNode *A, LNode *B, LNode *&C) {
LNode *p = A->next;
LNode *q = B->next;
LNode *s;
C = A;
C->next = NULL;
free(B);
while (p != NULL && q != NULL) {
if (p->data <= q->data) {
// 這裏就體現出s的作用了
// 如果沒有s,
// 則直接p->next = C->next,
// 那麼改變了p的指向,p無法再往後挪了
s = p;
p = p->next;
s->next = C->next;
C->next = s;
} else {
s = q;
q = q->next;
q->next = C->next;
C->next = s;
}
}
// 由於頭插,所以這裏必須循環將每個剩餘元素放到鏈表C的前面去
while (p != NULL) {
s = p;
p = p->next;
s->next = C->next;
C->next = s;
}
while (q != NULL) {
s = q;
q = q->next;
s->next = C->next;
C->next = s;
}
}
3.6)查找元素並刪除
int findAndDelete(LNode *list, int x) {
// 先找到該元素
LNode *p;
p = list;
while (p->next != NULL) {
if (p->next->data == x) {
break;
}
p = p->next;
}
// 然後刪除
if (p->next == NULL) {
return 0;
} else {
LNode *del;
del = p->next;
p->next = p->next->next;
free(del);
return 1;
}
}
3.7)對於一個遞增單鏈表,刪除其重複的元素
void deleteDuplicate(LNode *list) {
LNode *p, *q; // q用來釋放被刪除的元素
p = list->next;
while (p->next != NULL) {
if (p->data == p->next->data) {
q = p->next;
p->next = p->next->next;
free(q);
} else {
p = p->next;
}
}
}
3.8)刪除單鏈表中的最小值
void deleteMin(LNode *list) {
// 這題的關鍵是要弄清楚需要哪些指針:
// p用來掃描鏈表,pre指向p的前驅
// minp用來指向最小值節點,premin用來指向minp的前驅
LNode *p = list->next, *pre = list, *minp = p, *premin = pre;
while (p != NULL) {
if (p->data < minp->data) {
minp = p;
premin = pre;
}
pre = p;
p = p->next;
}
premin->next = minp->next;
free(minp);
}
3.9)單鏈表的原地逆置
// 將L->NULL設置爲空,然後將剩餘的節點一一用頭插法插入到L中
void reverseList(LNode *list) {
LNode *p = list->next, *q;
list->next = NULL; // 斷開頭節點
while (p != NULL) {
q = p->next; // 首先得讓q臨時保存一下p的下一個節點,待會兒還得用呢
p->next = list->next; // 頭插法
list->next = p;
p = q; // 拿回下一個節點
}
}
3.10)將一個數據域爲整數的單鏈表分割爲兩個分別爲奇數和偶數的鏈表
void split2(LNode *A, LNode *&B) {
// p用來掃描,但是指向的是要刪除節點的前驅
// q用來臨時保存要刪除的節點
// r用來指向B中的終端節點
LNode *p, *q, *r;
B = (LNode*)malloc(sizeof(LNode));
B->next = NULL; // 每申請一個新的節點,都將指針域設置爲空,這樣可以避免出事兒
p = A;
r = B;
while (p != NULL) {
if (p->next->data%2 == 0) {
q = p->next;
p->next = p->next->next;
r->next = q;
r = q;
q->next = NULL; // 不寫這句話絕對出事
} else {
p = p->next;
}
}
}
3.11)逆序打印單鏈表中的節點
// 逆序,入棧不就逆序了嗎,所以可以考慮遞歸打印
void reprintList(LNode *list) {
if (list != NULL) {
reprintList(list->next);
printf("%d ", list->data);
}
}
3.12)查找鏈表中倒數第k個節點
int findkLast(LNode *list, int k) {
LNode *p, *q;
p = list->next;
q = list;
int i = 1;
while (p != NULL) {
p = p->next;
++i; // 這裏的計數器i自己畫圖給弄清楚
if (i > k) q = q->next;
}
if (q == list) return 0; // 鏈表的節點不到k個
else {
printf("%d ", p->data);
return 1;
}
4. 雙鏈表的基本操作
4.1)尾插法建立雙鏈表
void createDListR(DLNode *&list, int a[], int n) {
// 準備工作...
DLNode *s, *r; // s指向新申請的節點,r指向終端節點
list = (DLNode*)malloc(sizeof(DLNode)); // 創建頭結點
list->prior = NULL;
list->next = NULL;
r = list; // 初始狀態,r指向頭結點
// 開始插入元素
int i;
for (int i = 0; i < n; i++) {
s = (DLNode*)malloc(sizeof(DLNode));
s->data = a[i];
// 最關鍵的三句話
r->next = s;
s->prior = r;
r = s;
}
r->next = NULL; // 如果不加這句話,一定會出事的
}
4.2)查找結點的算法
DLNode* findNode(DLNode *list, int x) {
DLNode *p = list->next;
while (p != NULL) {
if (p->data == x) {
break;
}
p = p->next;
}
return p;
}
4.3)插入結點的算法
int insertNode(DLNode *&list, int index, int e) {
if (index < 0) return 0;
DLNode *p = list;
for (int i = 0; i < index; ++i) {
p = p->next;
}
DLNode *s = (DLNode*)malloc(sizeof(DLNode));
s->data = e;
// 最關鍵的四句話:將新節點插入到p指向節點的後面
s->next = p->next;
s->prior = p;
p->next = s;
s->next->prior = s;
return 1;
}
4.4)刪除結點的算法
int deleteNode(DLNode *&list, int index) {
DLNode *p = list;
for (int i = 0; i < index; ++i) {
p = p->next;
}
if (p->next == NULL) return 0;
// 最關鍵的四句話:刪除p的後繼結點
DLNode *del;
del = p->next;
p->next = del->next;
if (del->next != NULL) { // 不加這個判斷也是會出事的
del->next->prior = p;
}
free(del);
return 1;
}
5. 棧和隊列的結構體定義
5.1)順序棧的定義
typedef struct SqStack{
int data[maxSize];
int top;
} SqStack;
5.2)鏈棧結點定義
typedef struct LNode{
int data;
struct LNode *next;
} LNode;
5.3)順序隊列的定義
typedef struct {
int data[maxSize];
int front; // 隊首指針
int rear; // 隊尾指針
}SqQueue;
5.4)鏈隊定義
(5.4.1)隊結點類型定義
typedef struct QNode {
int data; // 數據域
struct QNode *next; // 指針域
}QNode;
(5.4.2)鏈隊類型定義
typedef struct {
QNode *front; // 隊頭指針
QNode *rear; // 隊尾指針
}LiQueue;
6. 順序棧的基本操作
6.1)順序棧的初始化
void initStack(SqStack &st) {
st.top = -1;
}
6.2)判斷棧空
int isEmpty(SqStack st) {
return st.top == -1;
}
6.3)進棧代碼
int push(SqStack &st, int x) {
if (st.top == maxSize) return 0;
st.data[++st.top] = x;
return 1;
}
6.4)出棧代碼
int pop(SqStack &st, int &x) {
if (st.top == -1) return 0;
x = st.data[st.top--];
return 1;
}
6.5)考試中順序棧用法的簡潔寫法
int stack[maxSize]; int top = -1; // 這兩句話連定義帶初始化都有了
stack[++top] = x; // 一句話實現進棧
x = stack[top--]; // 一句話實現出棧
7. 鏈棧的基本操作
7.1)鏈棧的初始化
void initStack(LNode *&lst) {
lst = (LNode*)malloc(sizeof(LNode));
lst->next = NULL; // 還是老規矩:申請新的節點後一定要爲其指針域設置爲空
}
7.2)判斷棧空
int isEmpty(LNode *lst) {
return lst->next == NULL;
}
7.3)進棧代碼
void push(LNode *lst, int x) {
// 步驟一:申請新的節點,存放x的值
// 步驟二:頭插法將新的節點插入鏈棧中
LNode *p = (LNode*)malloc(sizeof(LNode));
p->next = NULL;
p->data = x;
p->next = lst->next;
lst->next = p;
}
7.4)出棧代碼
int pop(LNode *lst, int &x) {
// 步驟一:判空
// 步驟二:刪除節點
if (lst->next == NULL) return 0;
x = lst->data;
LNode *p;
p = lst->next;
lst->next = lst->next->next;
free(p);
return 1;
}
8. 順序隊列的基本操作
8.1)順序隊列的初始化
void initQueue(SqQueue &qu) {
qu.front = qu.rear = 0; // 隊首和隊尾指針重合,並且指向0
}
8.2)判斷隊空
int isQueueEmpty(SqQueue qu) {
return qu.front == qu.rear; // 只要重合,即爲空隊
}
8.3)進隊算法
int enQueue(SqQueue &qu, int x) {
if ((qu.rear+1)%maxSize == qu.front) return 0; // 隊滿則不能入隊
qu.rear = (qu.rear+1)%maxSize; // 若未滿,則先移動指針
qu.data[qu.rear] = x; // 再放入元素
return 1;
}
8.4)出隊算法
int deQueue(SqQueue &qu, int &x) {
if (qu.front == qu.rear) return 0; // 若隊空,則不能刪除
qu.front = (qu.front+1)%maxSize; // 若隊不空,則先移動指針
x = qu.data[qu.front]; // 再取出元素
return 1;
}
9. 鏈隊的基本操作
9.1)鏈隊的初始化
void initQueue(LiQueue *&lqu) {
lqu = (LiQueue*)malloc(sizeof(LiQueue));
lqu->front = lqu->rear = nullptr;
}
9.2)判斷隊空算法
int isQueueEmpty(LiQueue *lqu) {
return (lqu->rear == nullptr || lqu->front == nullptr); // 注意有兩個
}
9.3)入隊算法
void enQueue(LiQueue *lqu, int x) {
QNode *p;
p = (QNode*)malloc(sizeof(QNode));
p->data = x;
p->next = nullptr;
if (lqu->rear == nullptr) { // 若隊列爲空,則新結點是隊首結點,也是隊尾結點
lqu->front = lqu->rear = p;
} else {
lqu->rear->next = p; // 將新結點鏈接到隊尾,rear指向它
lqu->rear = p;
}
}
9.4)出隊算法
int deQueue(LiQueue *lqu, int &x) {
QNode *p;
if (lqu->rear == nullptr) { // 隊空不能出隊
return 0;
} else {
p = lqu->front;
}
if (lqu->front == lqu->rear) { // 隊列中只有一個結點時的出隊操作需要特殊處理
lqu->front = lqu->rear = nullptr;
} else {
lqu->front = lqu->front->next;
}
x = p->data;
free(p);
return 1;
}
10. 串的結構體定義
10.1)定長順序存儲表示
typedef struct {
char str[maxSize+1]; // 多出一個'\0'作爲結束標記
int length;
}Str;
10.2)變長分配存儲表示
typedef struct {
char *ch; // 指向動態分配存儲區首地址的字符指針
int length; // 串長度
}Str;
11. 串的基本操作
11.1)賦值操作
int strassign(Str& str, char* ch) {
if (str.ch) {
free(str.ch); // 釋放原空間
}
int len = 0;
char *c = ch;
while (*c) { // 求ch串的長度
++len;
++c;
}
if (len == 0) { // 如果ch爲空串,則直接返回空串
str.ch = nullptr;
str.length = 0;
return 1;
} else {
str.ch = (char*)malloc(sizeof(char)*(len+1)); // 取len+1是爲了'\0'
if (str.ch == nullptr) return 0; // 分配失敗
else {
c = ch;
for (int i = 0; i <= len; ++i, ++c) {
str.ch[i] = *c; // 之所以取<=也是爲了將'\0'複製過去
}
str.length = len;
return 1;
}
}
}
11.2)取串長度操作
int strlength(Str str) {
return str.length;
}
11.3)串比較操作
int strcompare(Str s1, Str s2) {
for (int i = 0; i < s1.length && i < s2.length; ++i) {
if (s1.ch[i] != s2.ch[i]) return s1.ch[i] - s2.ch[i];
}
return s1.length - s2.length;
}
11.4)串連接操作
int concat(Str &str, Str str1, Str str2) {
if (str.ch) {
free(str.ch); // 釋放原空間
str.ch = nullptr;
}
str.ch = (char*)malloc(sizeof(char)*(str1.length+str2.length));
if (str.ch == nullptr) return 0;
int i = 0;
while (i < str1.length) {
str.ch[i] = str1.ch[i];
++i;
}
int j = 0;
while (j <= str2.length) {
str.ch[i+j] = str2.ch[j]; // 之所以取<=也是爲了將'\0'複製過去
++j;
}
str.length = str1.length + str2.length;
return 1;
}
11.5)求子串操作
int substring(Str& substr, Str str, int pos, int len) {
if (pos<0 || pos>=str.length || len<0 || len>str.length-pos) return 0;
if (substr.ch) {
free(substr.ch);
substr.ch = nullptr;
}
if (len == 0) {
substr.ch = nullptr;
substr.length = 0;
return 1;
} else {
substr.ch = (char*)malloc(sizeof(char)*(len+1));
int i = pos;
int j = 0;
while (i<pos+len) {
substr.ch[j++] = str.ch[i++];
}
substr.ch[j] = '\0';
substr.length = len;
return 1;
}
}
11.6)串清空操作
int clearstring(Str& str) {
if (str.ch) {
free(str.ch);
str.ch = nullptr;
}
str.length = 0;
return 1;
}
12. 串的模式匹配
12.1)簡單模式匹配算法
int index(Str str, Str substr) {
int i = 1, j = 1, k = i;
while (i<=str.length && j<=substr) {
if (str.ch[i] == substr[j]) {
++i;
++j;
} else {
j = 1;
i = ++k; // 匹配失敗,i從主串的下一位置開始,k中記錄了上一次的起始位置
}
}
if (j>substr.length) return k;
else return 0;
}
12.2)KMP算法
注:這裏的代碼全部都認爲下標從0開始
(12.2.1)求next數組的代碼
void GetNext(char* p,int next[])
{
int pLen = strlen(p);
next[0] = -1;
int k = -1;
int j = 0;
while (j < pLen - 1)
{
//p[k]表示前綴,p[j]表示後綴
if (k == -1 || p[j] == p[k])
{
++k;
++j;
next[j] = k;
}
else
{
k = next[k];
}
}
}
(12.2.2)求nextval數組的代碼
//優化過後的next 數組求法
void GetNextval(char* p, int next[])
{
int pLen = strlen(p);
next[0] = -1;
int k = -1;
int j = 0;
while (j < pLen - 1)
{
//p[k]表示前綴,p[j]表示後綴
if (k == -1 || p[j] == p[k])
{
++j;
++k;
//較之前next數組求法,改動在下面4行
if (p[j] != p[k])
next[j] = k; //之前只有這一行
else
//因爲不能出現p[j] = p[ next[j ]],所以當出現時需要繼續遞歸,k = next[k] = next[next[k]]
next[j] = next[k];
}
else
{
k = next[k];
}
}
}
(12.2.3)KMP算法
int KMP(char* s, char* p, int next[])
{
int i = 0;
int j = 0;
int sLen = strlen(s);
int pLen = strlen(p);
while (i < sLen && j < pLen)
{
//①如果j = -1,或者當前字符匹配成功(即S[i] == P[j]),都令i++,j++
if (j == -1 || s[i] == p[j])
{
i++;
j++;
}
else
{
//②如果j != -1,且當前字符匹配失敗(即S[i] != P[j]),則令 i 不變,j = next[j]
//next[j]即爲j所對應的next值
j = next[j];
}
}
if (j == pLen)
return i - j; // 匹配成功,則返回匹配的位置
else
return -1;
}
13. 二叉樹的結點定義
13.1)普通二叉樹的鏈式存儲結點定義
typedef struct BTNode {
char data; // 這裏默認結點data域爲char類型
struct BTNode *lchild; // 左兒子
struct BTNode *rchild; // 右兒子
}BTNode;
13.2)線索二叉樹的結點定義
// 線索二叉樹
typedef struct TBTNode{
char data;
int ltag, rtag;
struct TBTNode *lchild;
struct TBTNode *rchild;
}TBTNode;
13. 二叉樹的遍歷算法
13.1)先序遍歷(遞歸)
void preorder(BTNode *p) {
if (p != nullptr) { // 一定要記得判空
printf("%c ", p->data);
preorder(p->lchild);
preorder(p->rchild);
}
}
13.2)中序遍歷(遞歸)
void inorder(BTNode *p) {
if (p != nullptr) {
inorder(p->lchild);
printf("%c ", p->data);
inorder(p->rchild);
}
}
13.3)後序遍歷(遞歸)
void postorder(BTNode *p) {
if (p != nullptr) {
postorder(p->lchild);
postorder(p->rchild);
printf("%c ", p->data);
}
}
13.4)層序遍歷
void level(BTNode *p) {
BTNode *que[maxSize];
BTNode *q = nullptr;
int front = 0, rear = 0; // 定義一個循環隊列
if (p != nullptr) {
rear = (rear+1) % maxSize;
que[rear] = p; // 讓根節點入隊
while (front != rear) { // 只要隊列不空,則進行循環
front = (front+1) % maxSize;
q = que[front]; // 隊頭元素出隊
printf("%c\n", q->data); // 訪問隊頭元素
if (q->lchild) { // 左子樹存在,則左子樹根節點入隊
rear = (rear+1) % maxSize;
que[rear] = q->lchild;
}
if (q->rchild) { // 右子樹存在,則右子樹根節點入隊
rear = (rear+1) % maxSize;
que[rear] = q->rchild;
}
}
}
13.5)先序遍歷(非遞歸)
void preorderNonrecursion(BTNode *bt) {
if (bt != nullptr) {
BTNode *Stack[maxSize];
int top = -1; // 定義人工棧
Stack[++top] = bt; // 根節點入棧
BTNode *p;
while (top != -1) { // 判斷不空
p = Stack[top--]; // 出棧 並完成一次訪問
printf("%c\n", p->data);
if (p->rchild != nullptr) { // 記得,先序遍歷一定是先右孩子,再左孩子
Stack[++top] = p->rchild;
}
if (p->lchild != nullptr) {
Stack[++top] = p->lchild;
}
}
}
}
13.6)中序遍歷(非遞歸)
void inorderNonrecursion(BTNode *bt) {
BTNode *Stack[maxSize];
int top = -1;
BTNode *p = bt;
if (bt != nullptr) {
while (top != -1 || p != nullptr) {
while (p != nullptr) {
Stack[++top] = p;
p = p->lchild;
}
if (top != -1) {
p = Stack[top--];
printf("%c\n", p->data);
p = p->rchild;
}
}
}
}
13.7)後序遍歷(非遞歸)
void postorderNonrecursion(BTNode *bt) {
if (bt != nullptr) {
BTNode *Stack1[maxSize]; int top1 = -1;
BTNode *Stack2[maxSize]; int top2 = -1; // 定義兩個棧
BTNode *p;
Stack1[++top1] = bt;
while (top1 != -1) {
p = Stack1[top1--];
Stack2[++top2] = p; // 注意這裏與先序的區別,放入棧2中即可
if (p->lchild) {
Stack1[++top1] = p->lchild;
}
if (p->rchild) {
Stack1[++top1] = p->rchild;
}
}
// 這時候循環結束,則會將逆後序遍歷的結果都存放到了棧2中
// 所以對棧2進行輸出即可得到後序遍歷的結果
while (top2 != -1) {
p = Stack2[top2--];
printf("%c\n", p->data);
}
}
}
14. 圖的存儲結構
14.1)鄰接矩陣的結點定義
typedef struct {
int no; // 頂點編號
char info; // 頂點的其他信息,這裏默認爲char型。這一句一般在題目中很少用到,因此題目不做特殊要求可以不寫
}VertexType; // 頂點類型
typedef struct {
int edges[maxSize][maxSize]; // 臨街矩陣定義,如果是有權圖,則在此句中將int改爲float
int n, e; // 分別爲定點數和邊數
VertexType vex[maxSize]; // 存放節點信息
}MGraph; // 圖的鄰接矩陣類型
14.2)鄰接表的結點定義
typedef struct ArcNode{
int adjvex; // 該邊所指向的節點的位置
struct ArcNode *nextarc; // 指向下一條邊的指針
int info; // 該邊的相關信息(如權值)
}ArcNode;
typedef struct {
char data; // 定點信息
ArcNode *firstarc; // 指向第一條邊的指針
}VNode;
typedef struct{
VNode adjlist[maxSize]; // 鄰接表
int n, e; // 定點數和邊數
}AGraph; // 圖的鄰接表類型
15. 圖的遍歷方式
15.1)DFS
int visit[maxSize];
void DFS(AGraph *G, int v) {
ArcNode *p;
visit[v] = 1;
cout << v << endl;
p = G->adjlist[v].firstarc; // 讓p指向頂點v的第一條邊
while (p != nullptr) {
if (visit[p->adjvex] == 0) {
DFS(G, p->adjvex);
p = p->nextarc;
}
}
}
15.2)BFS
void BFS(AGraph *G, int v) {
ArcNode *p;
int que[maxSize], front = 0, rear = 0; // 定義一個隊列
int j;
cout << v << endl;
visit[v] = 1;
rear = (rear+1)%maxSize; // 入隊
que[rear] = v;
while (front != rear) {
front = (front+1)%maxSize; // 頂點出隊
j = que[front];
p = G->adjlist[j].firstarc; // p指向出隊頂點j的第一條邊
while (p != nullptr) { // 將p的所有鄰接點未被訪問的入隊
if (visit[p->adjvex] == 0) {
cout << p->adjvex << endl;
rear = (rear+1)%maxSize;
que[rear] = p->adjvex;
}
p = p->nextarc;
}
}
}
16. 最小(代價)生成樹
16.1)Prim算法
void Prim(MGraph g, int v0, int &sum) {
int lowcost[maxSize], vset[maxSize], v;
int i, j, k, min;
v = v0;
for (i = 0; i < g.n; ++i) {
lowcost[i] = g.edges[v0][i];
vset[i] = 0;
}
vset[v0] = 1; // 將v0併入樹中
sum = 0; // sum清零用來累計樹的權值
for (i = 0; i < g.n-1; ++i) {
min = INF; // INF是一個已經定義的比圖中所有邊權值都大的常量
// 下面這個循環用於選出候選邊中的最小者
for (j = 0; j < g.n; ++j) {
if (vset[j] == 0 && lowcost[j] < min) { // 選出當前生成樹其他頂點到最短邊中最短的一條
min = lowcost[j];
k = j;
}
vset[k] = 1;
v = k;
sum += min; // 這裏用sum記錄了最小生成樹的權值
// 下面這個循環以剛進入的頂點v爲媒介更新候選邊
for (j = 0; j < g.n; ++j) {
if (vset[j] == 0 && g.edges[v][j] < lowcost[j]) {
lowcost[j] = g.edges[v][j];
}
}
}
}
}
16.2)Kruskal算法
typedef struct {
int a, b; // a和b爲一條邊所連的兩個頂點
int w; // 邊的權值
}Road;
Road road[maxSize];
int v[maxSize]; // 定義並查集數組
int getRoot(int a) {
while (a != v[a]) a = v[a]; // 在並查集中查找根結點的函數
return a;
}
void Kruskal(MGraph g, int &sum, Road road[]) {
int i, N, E, a, b;
N = g.n;
E = g.e;
sum = 0;
for (i = 0; i < N; ++i) v[i] = i;
sort(road, E); // 對road數組中的E條邊按其權值從小到大排序, 假設該函數已定義好
for (i = 0; i < E; ++i) {
a = getRoot(road[i].a);
b = getRoot(road[i].b);
if (a != b) {
v[a] = b;
sum += road[i].w;
}
}
}
17. 最短路徑算法
17.1)Dijkstra算法
void Dijkstra(MGraph g, int v, int dist[], int path[]) {
int set[maxSize];
int min, i, j, u;
// 從這句開始對各數組進行初始化
for (i = 0; i < g.n; ++i) {
dist[i] = g.edges[v][i];
set[i] = 0;
if (g.edges[v][i] < INF)
path[i] = v;
else {
path[i] = -1;
}
}
set[v] = 1; path[v] = -1;
// 初始化結束
// 關鍵操作開始
for (i = 0; i < g.n-1; ++i) {
min = INF;
// 這個循環每次從剩餘頂點中選出一個頂點,通往這個頂點的路徑在通往所有剩餘頂點的路徑中是長度最短的
for (j = 0; j < g.n; ++j) {
if (set[j] == 0 && dist[j] < min) {
u = j;
min = dist[j];
}
}
set[u] = 1; // 將選出的頂點併入最短路徑中
// 這個循環以剛併入的頂點作爲中間點,對所有通往剩餘頂點的路徑進行檢測
for (j = 0; j < g.n; ++j) {
// 這個if語句判斷頂點u的加入是否爲出現通往頂點j的更短的路徑
if (set[j] == 0 && dist[u]+g.edges[u][j] < dist[j]) {
dist[j] = dist[u] + g.edges[u][j];
path[j] = u;
}
}
}
}
17.2)Floyd算法
void Floyd(MGraph g, int Path[][maxSize]) {
int i, j, k;
int A[maxSize][maxSize];
// 這個雙循環對數組A[][]和Path[][]進行了初始化
for (i = 0; i < g.n; ++i) {
for (j = 0; j < g.n; ++j) {
A[i][j] = g.edges[i][j];
Path[i][j] = -1;
}
}
// 下面三層循環是主要操作,完成了以k爲中間點對所有的頂點對{i, }進行檢測和修改
for (k = 0; k < g.n; ++k) {
for (i = 0; i < g.n; ++i) {
for (j = 0; j < g.n; ++j) {
if (A[i][j] > A[i][k] + A[k][j]) {
A[i][j] = A[i][k] + A[k][j];
Path[i][j] = k;
}
}
}
}
}
18. 拓撲排序
18.1)拓撲排序中對鄰接表表頭結構的修改
typedef struct {
char data;
int count; // 此處爲新增代碼,count用來統計頂點當前的入度
ArcNode *firstarc;
}VNode;
18.2)拓撲排序算法
int TopSort(AGraph *G) {
int i, j, n = 0;
int stack[maxSize], top = -1; // 定義並初始化棧
ArcNode *p;
// 這個循環將圖中入度爲0的頂點入棧
for (i = 0; i < G->n; ++i) { // 圖中的頂點從0開始編號
if (G->adjlist[i].count == 0) {
stack[++top] = i;
}
}
// 關鍵操作開始
while (top != -1) {
i = stack[top--]; // 頂點出棧
++n; // 計數器加1,統計當前頂點
cout << i << " "; // 輸出當前頂點
p = G->adjlist[i].firstarc;
// 這個循環實現了將所有由此頂點引出的邊所指向的頂點的入度都減少1
// 並將這個過程中入度變爲0的頂點入棧
while (nullptr != p) {
j = p->adjvex;
--(G->adjlist[j].count);
if (G->adjlist[j].count == 0)
stack[++top] = j;
p = p->nextarc;
}
}
// 關鍵操作結束
return n == G->n;
}
19. 排序算法
19.1)直接插入排序
void InsertSort(int a[], int n) {
int i, j;
int temp;
for (i = 1; i < n; ++i) {
if (a[i] < a[i-1]) {
temp = a[i];
for (j = i; a[j-1] > temp && j >= 1; --j) {
a[j] = a[j-1];
}
a[j] = temp;
}
}
}
19.2)折半插入排序
void BinaryInsertSort(int R[],int n) {
int i,j,low,mid,high,temp;
for(i=1; i<n; i++) {
low=0;
high=i-1;
temp=R[i];
// 下面的折半是爲了在i元素的前面查找到合適的插入位置
while(low <= high) {
mid=(low+high)/2;
if(R[mid]>temp) {
high=mid-1;
} else {
low=mid+1;
}
}
for(j=i-1;j>=high+1;j--) {
R[j+1]=R[j];
}
R[j+1]=temp;
}
}
19.3)希爾排序
// 更小間隔的排序並沒有破壞之前的排序後的相對性,但是排序的結果可能不一樣了
void ShellSort(int arr[], int n) {
for (int d = n/2; d > 0; d /= 2) {
// 下面的內容就是把上面插入排序中所有的1改爲d,一共有5處修改,別忘了--j也要改
for (int i = d; i < n; ++i) {
int temp = arr[i];
int j;
for (j = i; j >= d && temp < arr[j-d]; j -= d) {
arr[j] = arr[j-d];
}
arr[j] = temp;
}
}
}
19.4)起泡排序
void BubbleSort(int arr[], int n) {
int temp;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n-i-1; ++j) {
if (arr[j] > arr[j+1]) {
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
19.5)快速排序
(19.5.1)兩路快排
void QuickSort(int arr[], int l, int r) {
if (l >= r) return;
swap(arr[l], arr[rand()%(r-l+1)+l]);
int v = arr[l];
// arr[l+1...i) <= v, arr(j...r] >= v
int i = l+1, j = r; // 一邊開,一邊閉,所以初始值取端點值即可
while (1) {
while (i <= r && arr[i] < v) ++i;
while (j >= l+1 && arr[j] > v) --j;
if (i > j) break;
swap(arr[i++], arr[j--]);
}
swap(arr[l], arr[j]);
int p = j;
QuickSort(arr, l, p-1);
QuickSort(arr, p+1, r);
}
(19.5.2)三路快排
// 三路快排(性能最好)
template <typename T>
void __quickSort3Ways(T arr[], int l, int r) {
if (l >= r) return;
// partition
swap(arr[l], arr[rand()%(r-l+1)+l]);
T v = arr[l];
int lt = l; // arr[l+1...lt] < v
int gt = r+1; // arr[gt...r] > v
int i = l+1; // arr[lt+1...i) == v
while (i < gt) {
if (arr[i] < v) {
swap(arr[++lt], arr[i++]);
} else if (arr[i] > v) {
swap(arr[--gt], arr[i]);
} else {
++i;
}
}
swap(arr[l], arr[lt]);
__quickSort3Ways(arr, l, lt-1);
__quickSort3Ways(arr, gt, r);
}
19.6)簡單選擇排序
void SelectSort(int arr[], int n) {
int temp;
for (int i = 0; i < n; ++i) {
for (int j = i+1; j < n; ++j) {
if (arr[i] > arr[j]) {
temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
}
}
19.7)堆排序
(19.7.1)shiftDown操作
// 在原地堆排序中,元素是從數組下標0的位置開始存儲的,因此i的左孩子應該爲2*i+1
template <typename T>
void __shiftDown(T arr[], int n, int k) {
while (2*k+1 < n) {
int j = 2*k + 1;
if (j + 1 < n && arr[j+1] > arr[j]) {
++j;
}
if (arr[k] >= arr[j]) break;
swap(arr[j], arr[k]);
k = j;
}
}
(19.7.2)堆排序代碼
template <typename T>
void heapSort3(T arr[], int n) {
// heapify
for (int i = (n-1)/2; i >= 0; --i) {
__shiftDown(arr, n, i);
}
for (int i = n-1; i > 0; --i) {
swap(arr[0], arr[i]);
__shiftDown(arr, i, 0);
}
}
19.8)歸併排序
(19.8.1) merge操作
// 將arr[l...mid]和arr[mid+1...r]進行歸併
template <typename T>
void __merge(T arr[], int l, int mid, int r) {
T aux[r-l+1];
for (int i = l; i <= r; ++i) {
aux[i-l] = arr[i];
}
int i = l, j = mid+1;
for (int k = l; k <= r; ++k) {
// 首先處理i, j越界
if (i > mid) {
arr[k] = aux[j-l];
++j;
} else if (j > r) {
arr[k] = aux[i-l];
++i;
} else if (aux[i-l] <= aux[j-l]) {
arr[k] = aux[i-l];
++i;
} else {
arr[k] = aux[j-l];
++j;
}
}
}
(19.8.2)遞歸的歸併排序算法
// 遞歸使用歸併排序,對arr[l...r]的範圍進行排序
template <typename T>
void __mergeSort(T arr[], int l, int r) {
if (l >= r) return;
int mid = l + (r-l)/2;
__mergeSort(arr, l, mid);
__mergeSort(arr, mid+1, r);
if (arr[mid] > arr[mid+1]) { // 優化處理,使之能夠更好處理近乎有序的數組
__merge(arr, l, mid, r);
}
}
(19.8.3)非遞歸的歸併排序算法
// 使用循環進行自底向上的歸併排序
template <typename T>
void mergeSortBU(T arr[], int n) {
for (int sz = 1; sz <= n; sz += sz) {
// 對arr[i...i+sz-1]和arr[i+sz...i+sz+sz-1]進行歸併
for (int i = 0; i + sz < n; i += sz+sz) {
// 細節一:i+sz < n是爲了防止前一段的右端點i+sz-1不會越界
if (arr[i+sz-1] > arr[i+sz]) {
__merge(arr, i, i+sz-1, min(i+sz+sz-1, n-1));
// 細節二:min在這裏的作用是爲了防止後一段的右端點i+sz+sz-1不會越界
}
}
}
}
/*由於自底向上的歸併排序沒有直接使用索引對數據進行操作,
因此可以方便應用於對鏈表這種結構進行排序的過程中*/
20. 折半查找法
int Bsearch(int arr[], int low, int high, int k) {
int mid;
while (low < high) {
mid = (low+high) / 2;
if (arr[mid] == k) {
return mid;
} else if (arr[mid] > k) {
high = mid - 1;
} else {
low = mid + 1;
}
}
return 0;
}
21. 二叉排序樹
21.1)二叉排序樹的結點定義
typedef struct BTNode {
int key;
struct BTNode *lchild;
struct BTNode *rchild;
}BTNode;
21.2)二叉排序樹的查找算法
BTNode* BSTSearch(BTNode *bt, int key) {
if (bt == nullptr) return nullptr;
if (bt->key == key) {
return bt;
} else if (key < bt->key) {
return BSTSearch(bt->lchild, key);
} else {
return BSTSearch(bt->rchild, key);
}
}
21.3)二叉排序樹的插入算法
int BSTInsert(BTNode *&bt, int key) { // 因爲bt要改變,所以要用引用型指針
if (bt == nullptr) {
bt = (BTNode*)malloc(sizeof(BTNode)); // 創建新結點
bt->lchild = bt->rchild = nullptr;
bt->key = key;
return 1;
} else {
if (key == bt->key) return 0; // 關鍵字已經存在於樹中
else if (key < bt->key) return BSTInsert(bt->lchild, key);
else return BSTInsert(bt->rchild, key);
}
}
21.4)二叉排序樹的構造算法
void CreateBST(BTNode *&bt, int key[], int n) {
bt = nullptr; // 將樹清空
for (int i = 0; i < n; ++i) {
BSTInsert(bt, key[i]);
}
}