1. 链表
1.1 单链表
单链表操作:
- 增加 (头插法与尾插法)
- 删除 (在删除的时候为了方便编程,会加入头结点。)
代码实现:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
int data;
struct Node* next;
} Node;
/* 初始化链表 */
Node* initList() { // 虚拟头结点
Node* list = (Node*)malloc(sizeof(Node));
list->data = 0;
list->next = NULL;
return list;
}
/* 头插法 */
void headInsert(Node* list, int data) {
Node* node = (struct Node*)malloc(sizeof(Node));
node->data = data;
node->next = list->next;
list->next = node;
list->data++;
}
/* 尾插法 */
void tailInsert(Node* list, int data) {
Node* head = list;
Node* node = (struct Node*)malloc(sizeof(Node));
node->data = data;
node->next = NULL;
list = list->next;
while (list->next) {
list = list->next;
}
list->next = node;
head->data++;
}
/* 删除节点 */
void delete(Node* list, int data) {
Node* pre = list;
Node* current = list->next;
while (current) {
if (current->data == data) {
pre->next = current->next;
free(current);
break;
}
pre = current;
current = current->next;
}
list->data--;
}
/* 打印链表 */
void printList(Node* list) {
list = list->next;
while (list) {
printf("%d ", list->data);
list = list->next;
}
printf("\r\n");
}
int main(int argc, char* argv[]) {
Node* list = initList();
headInsert(list, 1);
headInsert(list, 2);
headInsert(list, 3);
headInsert(list, 4);
headInsert(list, 5);
tailInsert(list, 6);
tailInsert(list, 7);
tailInsert(list, 8);
tailInsert(list, 9);
tailInsert(list, 10);
printList(list);
delete(list, 5);
delete(list, 10);
delete(list, 6);
printList(list);
return 0;
}
1.2 单循环链表
- 初始化链表
- 增加头结点(头插法、尾插法)
- 删除节点
- 遍历链表
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#define TRUE 1
#define FALSE 0
typedef struct Node {
int data;
struct Node* next;
} Node;
/* 初始化 */
Node* initList() {
Node* L = malloc(sizeof(Node)); // 虚拟头结点
L->data = 0;
L->next = L;
return L;
}
/* 头插法 */
void headInsert(Node* L, int data) {
Node* node = (Node*)malloc(sizeof(Node));
node->data = data;
node->next = L->next;
L->next = node;
L->data++;
}
/* 尾插法 */
void tailInsert(Node* L, int data) {
Node* n = L;
Node* node = (Node*)malloc(sizeof(Node));
node->data = data;
while (n->next != L) {
n = n->next;
}
node->next = L;
n->next = node;
}
/* 删除节点 */
int delete(Node* L, int data) {
Node* pre = L;
Node* cur = L->next;
while (cur != L) {
if (cur->data == data) {
pre->next = cur->next;
free(cur);
return TRUE;
}
pre = cur;
cur = cur->next;
}
return FALSE;
}
/* 打印单循环链表 */
void printList(Node* L) {
Node* node = L->next;
while (node != L) {
printf("%d->", node->data);
node = node->next;
}
printf("NULL\r\n");
}
int main(int argc, char* argv[]) {
Node* L = initList();
headInsert(L, 1);
headInsert(L, 2);
headInsert(L, 3);
headInsert(L, 4);
headInsert(L, 5);
printList(L);
tailInsert(L, 6);
tailInsert(L, 7);
printList(L);
delete(L, 4);
delete(L, 7);
printList(L);
return 0;
}
1.3 双链表
双链表相比于单链表不同在于有三个域。
- 初始化链表
- 插入节点(头插法、尾插法)
- 删除节点
- 遍历双链表
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#define TRUE 1
#define FALSE 0
typedef struct Node {
int data;
struct Node* pre;
struct Node* next;
} Node;
/* 初始化 */
Node* initList() {
Node* L = (Node*)malloc(sizeof(Node));
L->data = 0;
L->pre = NULL;
L->next = NULL;
return L;
}
/* 头插法 */
void headInsert(Node* L, int data) {
Node* node = (Node*)malloc(sizeof(Node));
node->data = data;
if (L->data == 0) { // 空链表
node->next = L->next;
node->pre = L;
L->next = node;
L->data++;
}
else {
node->pre = L;
node->next = L->next;
L->next->pre = node;
L->next = node;
L->data++;
}
}
/* 尾插法 */
void tailInsert(Node* L, int data) {
Node* n = L;
Node* node = (Node*)malloc(sizeof(Node));
node->data = data;
while (n->next) {
n = n->next;
}
node->next = n->next;
node->pre = n;
n->next = node;
L->data++;
}
/* 删除节点 */
int delete(Node* L, int data) {
Node* cur = L->next;
while (cur) {
if (cur->data == data) {
if (cur->next != NULL) { // 判断是否遍历到最后一个节点
cur->pre->next = cur->next;
cur->next->pre = cur->pre;
free(cur);
L->data--;
}
else {
cur->pre->next = NULL;
free(cur);
L->data--;
}
return TRUE;
}
cur = cur->next;
}
return FALSE;
}
/* 打印链表 */
void printList(Node* L) {
Node* node = L->next;
while (node) {
printf("%d->", node->data);
node = node->next;
}
printf("NULL\r\n");
}
int main(int argc, char* argv[]) {
return 0;
}
注意:
在双链表的删除节点的时候,需要格外注意的是要处理好删除最后的一个节点,因为最后一个节点的next指向的是NULL指针,如果不加判断将会操作NULL指针,这将会是灭顶之灾。
1.4 双向循环链表
- 初始化双向循环链表
- 增加节点(头插法、尾插法)
- 删除节点
- 遍历链表
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#define TRUE 1
#define FALSE 0
typedef struct Node {
int data;
struct Node* pre;
struct Node* next;
} Node;
/* 初始化 */
Node* initList() {
Node* L = (struct Node*)malloc(sizeof(Node));
L->data = 0;
L->pre = L;
L->next = L;
return L;
}
/* 头插法 */
void headInsert(Node* L, int data) {
Node* node = (Node*)malloc(sizeof(Node));
node->data = data;
if (L->data == 0) { // 空链表
node->next = L->next;
node->pre = L;
L->next = node;
L->pre = node;
}
else {
node->next = L->next;
node->pre = L;
L->next->pre = node;
L->next = node;
}
L->data++;
}
/* 尾插法 */
void tailInsert(Node* L, int data) {
Node* n = L;
Node* node = (Node*)malloc(sizeof(Node));
node->data = data;
while(n->next != L) {
n = n->next;
}
node->next = L;
node->pre = n;
n->next = node;
L->pre = node;
L->data++;
}
/* 删除节点 */
int delete(Node* L, int data) {
Node* cur = L->next;
while(cur != L) {
if (cur->data == data) {
cur->next->pre = cur->pre;
cur->pre->next = cur->next;
free(cur);
return TRUE;
}
cur = cur->next;
}
return FALSE;
}
/* 打印链表 */
void printList(Node* L) {
Node* node = L->next;
while (node != L) {
printf("%d->", node->data);
node = node->next;
}
printf("NULL\r\n");
}
int main(int argc, char* argv[]) {
Node* L = initList();
headInsert(L, 1);
headInsert(L, 2);
headInsert(L, 3);
headInsert(L, 4);
tailInsert(L, 5);
tailInsert(L, 6);
printList(L);
delete(L, 1);
delete(L, 2);
printList(L);
return 0;
}
相比于但循环链表,其有个需要注意的是双向循环链表在删除节点是不需要考虑删除最后一个节点会出错的问题,因为双循环链表不会出现NULL指针的问题。
2. 栈
栈的特点:先进后出
栈本质上来讲是一种特殊的线性表,也就是只能在一端进行操作,因而会使得存取的元素具有先进后出的特点。
应用:
- 表达式的值
- 解决一些递归问题
- 计算进制转换
实现的功能:
4. 初始化栈
5. 出栈
6. 入栈
7. 判断栈空
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
int data;
struct Node* next;
} Node;
/* 初始化 */
Node* initStack() {
Node* S = (Node*)malloc(sizeof(Node));
S->data = 0;
S->next = NULL;
return S;
}
/* 判断栈空 */
int isEmpty(Node* S) {
if (S->data == 0 || S->next == NULL) {
return 1;
}
else {
return 0;
}
}
/* 获取栈顶元素值 */
int getTop(Node* S) {
if (isEmpty(S)) {
return -1;
}
else {
return S->next->data;
}
}
/* 出栈 */
int pop(Node* S) {
if (isEmpty(S)) {
return -1;
}
else {
Node* cur = S->next;
int data = cur->data;
S->next = cur->next;
free(cur);
S->data--;
return data;
}
}
/* 入栈 */
void push(Node* S, int data) {
Node* node = (Node*)malloc(sizeof(Node));
node->data = data;
node->next = S->next;
S->next = node;
S->data++;
}
/* 打印栈中元素 */
void printStack(Node* S) {
Node* cur = S->next;
while (cur) {
printf("%d->", cur->data);
cur = cur->next;
}
printf("NULL\r\n");
}
int main(int argc, char* argv[]) {
Node* S = initStack();
push(S, 1);
push(S, 2);
push(S, 3);
push(S, 4);
printStack(S);
printf("%d\r\n", S->data);
int i = pop(S);
printf("pop's elemn is %d\r\n", i);
printStack(S);
printf("%d\r\n", S->data);
return 0;
}
3. 队列
队列的特点:先进先出
队列本质上也是一种特殊的线性表,只允许在一端进行存取,联想到之前线性表的尾插法。
实现的功能:
- 初始化队列
- 出队
- 入队
技巧:采用虚拟头结点去记录我们这个队列结点个数,这样在找最后一个节点的时候比较方便。
代码实现:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
int data;
struct Node* next;
} Node;
/* 初始化 */
Node* initQueue() {
Node* Q = (Node*)malloc(sizeof(Node));
Q->data = 0;
Q->next = NULL;
return Q;
}
/* 判断队空 */
int isEmpty(Node* Q) {
if(Q->data == 0 || Q->next == NULL) {
return 1;
}
else {
return 0;
}
}
/* 出队 */
int pop_back(Node* Q) {
if (isEmpty(Q)) {
return -1;
}
else {
Node* node = Q->next;
int data = node->data;
Q->next = node->next;
free(node);
Q->data--;
return data;
}
}
/* 入队 */
void push_back(Node* Q, int data) {
Node* q = Q;
Node* node = (Node*)malloc(sizeof(Node));
node->data = data;
for (int i = 0; i < Q->data; i++) {
q = q->next;
}
node->next = q->next;
q->next = node;
Q->data++;
}
/* 打印队列元素 */
void printQueue(Node* Q) {
Node* cur = Q->next;
while (cur) {
printf("%d->", cur->data);
cur = cur->next;
}
printf("NULL\r\n");
}
int main(int argc, char* argv[]) {
Node* Q = initQueue();
push_back(Q, 1);
push_back(Q, 2);
push_back(Q, 3);
push_back(Q, 4);
pop_back(Q);
printQueue(Q);
return 0;
}
4. 循环队列
初始状态下:front = rear;
队列满状态下:front = rear;
循环队列的难点:判断队列何时为空,何时为满。
特点:
- 牺牲掉队列一个空间,来去标识队列为空还是为满;
- 判断逻辑如下:
队空的话:front == rear;
队满的话:(rear+1)%MAX_SIZE == front;
实现的功能:
3. 初始化队列
4. 入队
5. 出队
6. 遍历循环队列
代码实现:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#define MAX_SIZE 5
typedef struct Queue {
int front;
int rear;
int data[MAX_SIZE];
} Queue;
/* 初始化 */
Queue* initQueue() {
Queue* Q = (Queue*)malloc(sizeof(Queue));
Q->front = Q->rear = 0;
return Q;
}
/* 判断队满 */
int isFull(Queue* Q) {
if ((Q->rear + 1) % MAX_SIZE == Q->rear) {
return 1;
}
else {
return 0;
}
}
/* 判断队空 */
int isEmpty(Queue* Q) {
if (Q->front == Q->rear) {
return 1;
}
else {
return 0;
}
}
/* 入队 */
int push_back(Queue* Q, int data) {
if (isFull(Q)) {
return -1;
}
else {
Q->data[Q->rear] = data;
Q->rear = (Q->rear + 1) % MAX_SIZE;
return data;
}
}
/* 出队 */
int pop_back(Queue* Q) {
if (isEmpty(Q)) {
return -1;
}
else {
int data = Q->data[Q->front];
Q->front = (Q->front + 1) % MAX_SIZE;
return data;
}
}
/* 打印队列元素 */
void printQueue(Queue* Q) {
int length = ((Q->rear - Q->front) + MAX_SIZE) % MAX_SIZE; // 计算循环队列长度
int index = Q->front;
for (int i = 0; i < length; i++) {
printf("%d->", Q->data[index]);
index = (index + 1) % MAX_SIZE; // 防止下标越界
}
printf("NULL\r\n");
}
int main(int argc, char* argv[]) {
Queue* Q = initQueue();
push_back(Q, 1);
push_back(Q, 2);
push_back(Q, 3);
push_back(Q, 4);
printQueue(Q);
pop_back(Q);
printQueue(Q);
return 0;
}
5. 二叉树
5.1 二叉树的创建与遍历
树:
树是一个n节点的有限集,如果n=0,那么称之为空树。
树的特点:
- 树的定义是递归的,树的定义中又用到了自身;
- 树的根节点没有前驱,除根节点以外,其它所有节点有且仅有一个前驱;
- 数中的所有节点可以有0个或者多个后驱;
二叉树:
- 一种特殊的树形结构;
- 每个节点至多只有两个子树;
遍历方式:
前中后指的是根节点遍历的顺序,理解递归可以想一想栈的思想。
- 前序:根->左->右 ABDECFG
- 中序:左->根->右 DBEAFCG
- 后序:左->右->根 DEBFGCA
实现的功能:
4. 二叉树的创建
5. 二叉树的遍历
代码实现:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct TreeNode {
char data;
struct TreeNode* left;
struct TreeNode* right;
} TreeNode;
/* 输入变长字符串 */
char* inputStr() {
char* str = NULL;
char ch;
const int step = 5; // 增幅
int allStrLen = 5; // 字符串初始长度
int loc = 0; // 记录索引
printf("Please input information of TreeNode('#' is symbol empty node): \r\n");
str = (char*)malloc(allStrLen * sizeof(char));
if (str == NULL) {
printf("Failed to request memory\r\n");
exit(0);
}
memset(str, 0, allStrLen * sizeof(char));
while ((ch = getchar()) != '\n' && ch != EOF) {
if (loc < allStrLen - 1) {
*(str + loc) = ch;
loc++;
continue;
}
char* tempStr = (char*)malloc(allStrLen * sizeof(char));
if(tempStr == NULL) {
printf("Failed to request memory\r\n");
free(str);
exit(0);
}
memset(tempStr, 0, allStrLen * sizeof(char));
memcpy(tempStr, str, loc * sizeof(char));
allStrLen += step;
free(str);
str = (char*)malloc(allStrLen * sizeof(char));
if (str == NULL) {
printf("The input is too long. Failed to request memory!\n\r");
free(tempStr);
exit(0);
}
memset(str, 0, allStrLen * sizeof(char));
memcpy(str, tempStr, loc * sizeof(char));
*(str + loc) = ch;
loc++;
free(tempStr);
}
return str;
}
/* 构建二叉树 */
void createTree(TreeNode** root, char* data, int* index) {
char ch = data[*index];
(*index)++;
if (ch == '#') {
*root = NULL;
return;
}
else {
*root = (TreeNode*)malloc(sizeof(TreeNode));
(*root)->data = ch;
createTree(&((*root)->left), data, index); // 构建左子树
createTree(&((*root)->right), data, index); // 构建右子树
}
}
/* 前序 */
void preOrder(TreeNode* root) {
if (root == NULL) {
return;
}
else {
printf("%c ", root->data); // 根
preOrder(root->left); // 左
preOrder(root->right); // 右
}
}
/* 中序 */
void inOrder(TreeNode* root) {
if (root == NULL) {
return;
}
else {
inOrder(root->left); // 左
printf("%c ", root->data); // 根
inOrder(root->right); // 右
}
}
/* 后序 */
void postOrder(TreeNode* root) {
if (root == NULL) {
return;
}
else {
postOrder(root->left); // 左
postOrder(root->right); // 右
printf("%c ", root->data); // 根
}
}
int main(int argc, char* argv[]) {
char* str = inputStr();
printf("Input TreeNode's is \"%s\"\r\n", str);
TreeNode* root = NULL;
int index = 0;
createTree(&root, str, &index);
preOrder(root);
printf("\r\n");
inOrder(root);
printf("\r\n");
postOrder(root);
free(str);
return 0;
}
注意:上面的二叉树的创建时可以直接从键盘中输入变长的字符,思想就是空间不足扩容。
5.2 二叉树的层序遍历
如何实现层次遍历:
队的数据结构去实现。
实现的功能:
- 队相关功能
- 新建树的功能
代码实现:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define TRUE 1
#define FALSE 0
/* 树节点 */
typedef struct TreeNode {
char data;
struct TreeNode* left;
struct TreeNode* right;
} TreeNode;
/* 队列结点 */
typedef struct QueueNode {
int length; // 链表伪长度
TreeNode* data; // 数据域
struct QueueNode* pre; // 前驱
struct QueueNode* next; // 后继
} QueueNode;
/* 不定长字符串输入 */
char* inputStr() {
char* str = NULL;
char ch;
int loc = 0;
int step = 5;
int allStrLen = 5;
printf("Please Input information of TreeNode:\r\n");
str = (char*)malloc(allStrLen * sizeof(char));
if (str == NULL) {
printf("Failed to request memory!\r\n");
exit(0);
}
memset(str, 0, allStrLen * sizeof(char));
while ((ch = getchar()) != '\n' && ch != EOF) {
if (loc < allStrLen - 1) {
*(str + loc) = ch;
loc++;
}
char* tempStr = (char*)malloc(allStrLen * sizeof(char));
if (tempStr == NULL) {
printf("Failed to request memory!\r\n");
free(str);
exit(0);
}
memset(tempStr, 0, allStrLen * sizeof(char));
memcpy(tempStr, str, loc * sizeof(char));
allStrLen += step;
free(str);
str = (char*)malloc(allStrLen * sizeof(char));
memset(str, 0, allStrLen * sizeof(char));
memcpy(str, tempStr, loc * sizeof(char));
*(str + loc) = ch;
loc++;
free(tempStr);
}
return str;
}
/*
*双向循环链表
*/
/* 初始化 */
QueueNode* initQueue() {
QueueNode* Q = (QueueNode*)malloc(sizeof(QueueNode));
Q->length = 0;
Q->data = NULL;
Q->pre = Q;
Q->next = Q;
return Q;
}
/* 判断队空 */
int isEmpty(QueueNode* Q) {
if (Q->next == Q) {
return TRUE;
}
else {
return FALSE;
}
}
/* 入队 */
void push_back(TreeNode* data, QueueNode* Q) {
QueueNode* node = (QueueNode*)malloc(sizeof(QueueNode));
node->data = data;
node->pre = Q->pre;
node->next = Q;
Q->pre->next = node;
Q->pre = node;
Q->length++;
}
/* 出队 */
QueueNode* pop_back(QueueNode* Q) {
if (isEmpty(Q)) {
return NULL;
}
else {
QueueNode* node = Q->next;
Q->next->next->pre = Q;
Q->next = Q->next->next;
return node;
}
}
/*
* 二叉树
*/
/* 构建二叉树 */
void createTree(TreeNode** T, char* data, int* index) {
char ch = data[*index];
(*index)++;
if (ch == '#') { // '#'标识空节点
(*T) = NULL;
return;
}
else {
(*T) = (TreeNode*)malloc(sizeof(TreeNode));
(*T)->data = ch;
createTree(&((*T)->left), data, index);
createTree(&((*T)->right), data, index);
}
}
/* 层序遍历 */
void levelTraverse(TreeNode* T, QueueNode* Q) {
push_back(T, Q);
while (!isEmpty(Q)) {
QueueNode* node = pop_back(Q);
printf("%c ", node->data->data);
if (node->data->left) {
push_back(node->data->left, Q);
}
if (node->data->left) {
push_back(node->data->right, Q);
}
free(node);
}
}
int main(int argc, char* argv[]) {
TreeNode* root = NULL;
int index = 0;
char* data = inputStr();
QueueNode* queue = initQueue();
createTree(&root, data, &index);
levelTraverse(root, queue);
free(data);
return 0;
}
注意:要做好内存的管理,当出队完之后,要释放出队节点所占用的内存。
5.3 二叉树的非递归遍历(前序+中序+后序)
代码实现:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define TRUE 1
#define FALSE 0
/* 树节点 */
typedef struct TreeNode {
char data;
int flag; // 节点访问标记
struct TreeNode* left;
struct TreeNode* right;
} TreeNode;
/* 栈节点 */
typedef struct StackNode {
int length;
TreeNode* data;
struct StackNode* next;
} StackNode;
/* 输入变长字符串 */
char* inputStr() {
char* str = NULL;
char ch;
const int step = 5; // 增幅
int allStrLen = 5; // 字符串初始长度
int loc = 0; // 记录索引
printf("Please input information of TreeNode: \r\n");
str = (char*)malloc(allStrLen * sizeof(char));
if (str == NULL) {
printf("Failed to request memory\r\n");
exit(0);
}
memset(str, 0, allStrLen * sizeof(char));
while ((ch = getchar()) != '\n' && ch != EOF) { // 判断是否到达末尾
if (loc < allStrLen - 1) {
*(str + loc) = ch;
loc++;
continue;
}
char* tempStr = (char*)malloc(allStrLen * sizeof(char));
if (tempStr == NULL) {
printf("Failed to request memory\r\n");
free(str);
exit(0);
}
memset(tempStr, 0, allStrLen * sizeof(char));
memcpy(tempStr, str, loc * sizeof(char));
allStrLen += step; // 新增长度
free(str);
str = (char*)malloc(allStrLen * sizeof(char));
if (str == NULL) {
printf("The input is too long. Failed to request memory!\n\r");
free(tempStr);
exit(0);
}
memset(str, 0, allStrLen * sizeof(char));
memcpy(str, tempStr, loc * sizeof(char));
*(str + loc) = ch;
loc++;
free(tempStr);
}
return str;
}
/*
* 栈
*/
/* 初始化 */
StackNode* initStack() {
StackNode* S = (StackNode*)malloc(sizeof(StackNode));
S->length = 0;
S->data = NULL;
S->next = NULL;
return S;
}
/* 判断栈空 */
int isEmpty(StackNode* S) {
if (S->next == NULL) {
return TRUE;
}
else {
return FALSE;
}
}
/* 入栈 */
void push(TreeNode* data, StackNode* S) {
StackNode* node = (StackNode*)malloc(sizeof(StackNode));
node->data = data;
node->next = S->next;
S->next = node;
S->length++;
}
/* 出栈 */
StackNode* pop(StackNode* S) {
if (isEmpty(S)) {
return NULL;
}
else {
StackNode* node = S->next;
S->next = node->next;
S->length--;
return node;
}
}
/*
* 二叉树
*/
/* 构建二叉树 */
void createTree(TreeNode** T, char* data, int* index) {
char ch = data[*index];
(*index)++;
if (ch == '#') { // '#'标识空节点
(*T) = NULL;
return;
}
else {
(*T) = (TreeNode*)malloc(sizeof(TreeNode));
(*T)->data = ch;
(*T)->flag = 0;
createTree(&((*T)->left), data, index);
createTree(&((*T)->right), data, index);
}
}
/* 前序 */
void preOrder(TreeNode* T) {
TreeNode* node = T;
StackNode* S = initStack();
while (node || !isEmpty(S)) {
if (node) {
printf("%c ", node->data);
push(node, S);
node = node->left;
}
else {
node = pop(S)->data;
node = node->right;
}
}
}
/* 中序 */
void inOrder(TreeNode* T) {
TreeNode* node = T;
StackNode* S = initStack();
while (node || !isEmpty(S)) {
if (node) {
push(node, S);
node = node->left;
}
else {
node = pop(S)->data;
printf("%c ", node->data);
node = node->right;
}
}
}
StackNode* getTop(StackNode* S) {
if (isEmpty(S)) {
return NULL;
}
else {
StackNode* node = S->next;
return node;
}
}
/* 后序 */
void postOrder(TreeNode* T) {
TreeNode* node = T;
StackNode* S = initStack();
while (node || !isEmpty(S)) {
if (node) {
push(node, S);
node = node->left;
}
else {
TreeNode* top = getTop(S)->data;
if (top->right && top->right->flag == 0) {
top = top->right;
push(top, S);
node = top->left;
}
else {
top = pop(S)->data;
printf("%c ", top->data);
top->flag = 1;
}
}
}
}
int main(int argc, char* argv[]) {
TreeNode* root;
int index = 0;
char* data = inputStr();
createTree(&root, data, &index);
preOrder(root);
printf("\r\n");
inOrder(root);
printf("\r\n");
postOrder(root);
free(data);
return 0;
}
5.4 中序线索二叉树
什么是线索二叉树?
树是否是一个线性结构? 答案是否定的,因为线性结构有前驱和后继,我们使用线索将二叉树转换成一个类似的线性结构。
什么是线索? 线索其实就是将节点连接在一起的指针;如果一个二叉树有n个节点,那么有多少个指针指向这些节点? 有n-1个指针指向它们,一共有多少个指针? 有2n个指针,2n-(n-1)=n+1个指针没有用,利用这n+1个指针,来指向我们二叉树遍历序列当中的前驱和后继。
实现的功能:
- 建立一棵线索二叉树
- 遍历线索二叉树
ltag = 0 left; (==0表示指向NULL,也就是该左指针没有用到)
rtag = 0 right; (如果没有右孩子,那么rtag=1)
代码实现:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define TRUE 1
#define FALSE 0
/* 树节点 */
typedef struct TreeNode {
char data;
int ltag;
int rtag;
struct TreeNode* left;
struct TreeNode* right;
} TreeNode;
/* 输入变长字符串 */
char* inputStr() {
char* str = NULL;
char ch;
const int step = 5; // 增幅
int allStrLen = 5; // 字符串初始长度
int loc = 0; // 记录索引
printf("Please input information of TreeNode: \r\n");
str = (char*)malloc(allStrLen * sizeof(char));
if (str == NULL) {
printf("Failed to request memory\r\n");
exit(0);
}
memset(str, 0, allStrLen * sizeof(char));
while ((ch = getchar()) != '\n' && ch != EOF) { // 判断是否到达末尾
if (loc < allStrLen - 1) {
*(str + loc) = ch;
loc++;
continue;
}
char* tempStr = (char*)malloc(allStrLen * sizeof(char));
if (tempStr == NULL) {
printf("Failed to request memory\r\n");
free(str);
exit(0);
}
memset(tempStr, 0, allStrLen * sizeof(char));
memcpy(tempStr, str, loc * sizeof(char));
allStrLen += step; // 新增长度
free(str);
str = (char*)malloc(allStrLen * sizeof(char));
if (str == NULL) {
printf("The input is too long. Failed to request memory!\n\r");
free(tempStr);
exit(0);
}
memset(str, 0, allStrLen * sizeof(char));
memcpy(str, tempStr, loc * sizeof(char));
*(str + loc) = ch;
loc++;
free(tempStr);
}
return str;
}
/*
* 二叉树
*/
/* 构建二叉树 */
void createTree(TreeNode** T, char* data, int* index) {
char ch = data[*index];
(*index)++;
if (ch == '#') { // '#'标识空节点
(*T) = NULL;
return;
}
else {
(*T) = (TreeNode*)malloc(sizeof(TreeNode));
(*T)->data = ch;
(*T)->ltag = 0;
(*T)->rtag = 0;
createTree(&((*T)->left), data, index);
createTree(&((*T)->right), data, index);
}
}
/* 构建线索二叉树(中序) */
void inThreadTree(TreeNode* T, TreeNode** pre) {
if (T) {
inThreadTree(T->left, pre);
if (T->left == NULL) {
T->ltag = 1;
T->left = *pre;
}
if (*pre != NULL && (*pre)->right == NULL) {
(*pre)->rtag = 1;
(*pre)->right = T;
}
*pre = T;
inThreadTree(T->right, pre);
}
}
/* 获取第一个元素 */
TreeNode* getFirst(TreeNode* T) {
while (T->ltag == 0)
T = T->left;
return T;
}
/* 获取下一个元素 */
TreeNode* getNext(TreeNode* node) {
if (node->rtag == 1)
return node->right;
else
return getFirst(node->right);
}
int main(int argc, char* argv[]) {
TreeNode* root = NULL;
TreeNode* pre = NULL;
int index = 0;
char* data = inputStr();
createTree(&root, data, &index);
inThreadTree(root, &pre);
pre->rtag = 1;
pre->right = NULL;
for (TreeNode* node = getFirst(root); node != NULL; node = getNext(node)) {
printf("%c ", node->data);
}
printf("\r\n");
free(data);
return 0;
}
5.5 先序线索二叉树
代码实现:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define TRUE 1
#define FALSE 0
/* 树节点 */
typedef struct TreeNode {
char data;
int ltag;
int rtag;
struct TreeNode* left;
struct TreeNode* right;
} TreeNode;
/* 输入变长字符串 */
char* inputStr() {
char* str = NULL;
char ch;
const int step = 5; // 增幅
int allStrLen = 5; // 字符串初始长度
int loc = 0; // 记录索引
printf("Please input information of TreeNode: \r\n");
str = (char*)malloc(allStrLen * sizeof(char));
if (str == NULL) {
printf("Failed to request memory\r\n");
exit(0);
}
memset(str, 0, allStrLen * sizeof(char));
while ((ch = getchar()) != '\n' && ch != EOF) { // 判断是否到达末尾
if (loc < allStrLen - 1) {
*(str + loc) = ch;
loc++;
continue;
}
char* tempStr = (char*)malloc(allStrLen * sizeof(char));
if (tempStr == NULL) {
printf("Failed to request memory\r\n");
free(str);
exit(0);
}
memset(tempStr, 0, allStrLen * sizeof(char));
memcpy(tempStr, str, loc * sizeof(char));
allStrLen += step; // 新增长度
free(str);
str = (char*)malloc(allStrLen * sizeof(char));
if (str == NULL) {
printf("The input is too long. Failed to request memory!\n\r");
free(tempStr);
exit(0);
}
memset(str, 0, allStrLen * sizeof(char));
memcpy(str, tempStr, loc * sizeof(char));
*(str + loc) = ch;
loc++;
free(tempStr);
}
return str;
}
/*
* 二叉树
*/
/* 构建二叉树 */
void createTree(TreeNode** T, char* data, int* index) {
char ch = data[*index];
(*index)++;
if (ch == '#') { // '#'标识空节点
(*T) = NULL;
return;
}
else {
(*T) = (TreeNode*)malloc(sizeof(TreeNode));
(*T)->data = ch;
(*T)->ltag = 0;
(*T)->rtag = 0;
createTree(&((*T)->left), data, index);
createTree(&((*T)->right), data, index);
}
}
/* 构建线索二叉树(中序) */
void preThreadTree(TreeNode* T, TreeNode** pre) {
if (T) {
if (T->left == NULL) {
T->ltag = 1;
T->left = *pre;
}
if (*pre != NULL && (*pre)->right == NULL) {
(*pre)->rtag = 1;
(*pre)->right = T;
}
*pre = T;
if(T->ltag == 0)
preThreadTree(T->left, pre);
preThreadTree(T->right, pre);
}
}
/* 获取下一个元素 */
TreeNode* getNext(TreeNode* node) {
if (node->rtag == 1 || node->ltag == 1)
return node->right;
else
return node->left;
}
int main(int argc, char* argv[]) {
TreeNode* root = NULL;
TreeNode* pre = NULL;
int index = 0;
char* data = inputStr();
createTree(&root, data, &index);
preThreadTree(root, &pre);
pre->rtag = 1;
pre->right = NULL;
for (TreeNode* node = root; node != NULL; node = getNext(node)) {
printf("%c ", node->data);
}
printf("\r\n");
free(data);
return 0;
}
5.6 后序线索二叉树
寻找第一个节点:
- 找到最左边的节点
- 判断这个节点,是否有右子树,如果有的话,继续寻找以右子树为根树的最左边节点,如果没有,那么第一个节点就是最左边的。
寻找下一个节点:
3. 是根节点,next = NULL
4. 是左孩子,那么判断父亲右子树是否为空,如果右子树为空,next = parent;如果右子树不为空,next = getFirst(parent->right);
5. 是右孩子,next = parent
什么是二叉链表? 就是只有指针域left和right。
什么是三叉链表? 就是再增加一个指针域parent。
代码实现:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define TRUE 1
#define FALSE 0
/* 树节点 */
typedef struct TreeNode {
char data;
int ltag;
int rtag;
struct TreeNode* left;
struct TreeNode* right;
struct TreeNode* parent;
} TreeNode;
/* 输入变长字符串 */
char* inputStr() {
char* str = NULL;
char ch;
const int step = 5; // 增幅
int allStrLen = 5; // 字符串初始长度
int loc = 0; // 记录索引
printf("Please input information of TreeNode: \r\n");
str = (char*)malloc(allStrLen * sizeof(char));
if (str == NULL) {
printf("Failed to request memory\r\n");
exit(0);
}
memset(str, 0, allStrLen * sizeof(char));
while ((ch = getchar()) != '\n' && ch != EOF) { // 判断是否到达末尾
if (loc < allStrLen - 1) {
*(str + loc) = ch;
loc++;
continue;
}
char* tempStr = (char*)malloc(allStrLen * sizeof(char));
if (tempStr == NULL) {
printf("Failed to request memory\r\n");
free(str);
exit(0);
}
memset(tempStr, 0, allStrLen * sizeof(char));
memcpy(tempStr, str, loc * sizeof(char));
allStrLen += step; // 新增长度
free(str);
str = (char*)malloc(allStrLen * sizeof(char));
if (str == NULL) {
printf("The input is too long. Failed to request memory!\n\r");
free(tempStr);
exit(0);
}
memset(str, 0, allStrLen * sizeof(char));
memcpy(str, tempStr, loc * sizeof(char));
*(str + loc) = ch;
loc++;
free(tempStr);
}
return str;
}
/*
* 二叉树
*/
/* 构建二叉树 */
void createTree(TreeNode** T, char* data, int* index, TreeNode* parent) {
char ch = data[*index];
(*index)++;
if (ch == '#') { // '#'标识空节点
(*T) = NULL;
return;
}
else {
(*T) = (TreeNode*)malloc(sizeof(TreeNode));
(*T)->data = ch;
(*T)->ltag = 0;
(*T)->rtag = 0;
(*T)->parent = parent;
createTree(&((*T)->left), data, index, *T);
createTree(&((*T)->right), data, index, *T);
}
}
/* 构建线索二叉树(后序) */
void postThreadTree(TreeNode* T, TreeNode** pre) {
if (T) {
postThreadTree(T->left, pre);
postThreadTree(T->right, pre);
if (T->left == NULL) {
T->ltag = 1;
T->left = *pre;
}
if (*pre != NULL && (*pre)->right == NULL) {
(*pre)->rtag = 1;
(*pre)->right = T;
}
*pre = T;
}
}
/* 获取第一个元素 */
TreeNode* getFirst(TreeNode* T) {
while (T->ltag == 0) {
T = T->left;
}
if (T->rtag == 0)
return getFirst(T->right);
return T;
}
/* 获取下一个元素 */
TreeNode* getNext(TreeNode* node) {
if (node->rtag == 1)
return node->right;
else
if (node->parent == NULL) { // 如果是根节点
return NULL;
}
else if (node->parent->right == node) { // 如果是右孩子
return node->parent;
}
else { // 如果是左孩子
if (node->parent->ltag == 0) {
return getFirst(node->parent->right);
}
else {
return node->parent;
}
}
}
int main(int argc, char* argv[]) {
TreeNode* root = NULL;
TreeNode* pre = NULL;
int index = 0;
char* data = inputStr();
createTree(&root, data, &index, NULL);
postThreadTree(root, &pre);
for (TreeNode* node = getFirst(root); node != NULL; node = getNext(node)) {
printf("%c ", node->data);
}
printf("\r\n");
free(data);
return 0;
}
5.7 二叉搜索树(BST)
Binary Search Tree
什么是BST?
一棵树,左子树上的所有节点的值,都比根节点小;右子树上的所有节点的值,都比根节点大,;同时,这个性质是递归的,同时,树中的节点值不重复。
如何构建BST?
序列:4, 5, 19, 23, 2, 8
实现的功能:
- 建立一棵二叉排序树
- 在二叉树中查找值
代码实现:
#include <stdio.h>
#include <stdlib.h>
typedef struct TreeNode {
int data;
struct TreeNode* left;
struct TreeNode* right;
} TreeNode;
/* 查找 */
TreeNode* bstSearch(TreeNode* T, int data) {
if (T == NULL) {
return NULL;
}
else {
if (data == T->data) {
return T;
}
else if (data < T->data) {
return bstSearch(T->left, data);
}
else {
return bstSearch(T->right, data);
}
}
}
/* 插值 */
void bstInsert(TreeNode** T, int data) {
if (*T == NULL) {
*T = (TreeNode*)malloc(sizeof(TreeNode));
(*T)->data = data;
(*T)->left = NULL;
(*T)->right = NULL;
}
else if (data == (*T)->data) {
return;
}
else if (data < (*T)->data) {
bstInsert(&(*T)->left, data);
}
else {
bstInsert(&(*T)->right, data);
}
}
/* 前序 */
void preOrder(TreeNode* T) {
if (T == NULL) {
return;
}
printf("%d ", T->data);
preOrder(T->left);
preOrder(T->right);
}
int main(int argc, char* argv[]) {
TreeNode* T = NULL;
int nums[6] = { 4, 2, 5, 11, 3, 25 }; // 测试集
for (int i = 0; i < 6; i++) { // 构建二叉搜索树
bstInsert(&T, nums[i]);
}
preOrder(T); // 前序遍历
printf("\r\n");
return 0;
}
5.8 平衡二叉树(AVL)
5.8.1 原理剖析
什么是平衡二叉树(AVL) ?
平衡二叉树是一棵合理的二叉排序树。
怎么保证合理?
平衡二叉树的左右子树高度差不超过1,减少树的深度,从而提高查找的效率。正如上图所示,如果用单纯的二叉搜索树,查找5的话需要比较五次,但是用左边的这棵平衡二叉树的话仅需要查询三次就可以。
如何构建一棵平衡二叉树?
- 本质上和构建二叉排序树一致;
- 在构建二叉排序树的过程中,如果发现不符合特性,需要进行调整,调整分为四种类型,LL,RR,RL,LR。如果遇到多棵树不平衡,选择最小树。
如何判断调整类型?
- 找到失衡树的根节点 root
- 找到导致树失衡的节点 node,node 在 root 的哪一侧
- 判断 node 在 root 孩子的哪一侧
调整类型分为四种情况:LL、RR、LR、RL,关于这几种情况的具体介绍可以参考我的笔记。
笔记链接:https://www.yuque.com/u28724735/yhq8gs/wq3e99doyt0deplw/edit#ghE1f
5.8.2 代码实现
整体思路:
- 建立平衡二叉树的过程就是建立一棵二叉排序树的过程;
- 在建立过程中我们需要去进行调整,调整需要用到树的高度,所以我们节点的结构体当中需要加入一个字段来标识当前树的高度;
- 调整方法。
代码实现:
#include <stdio.h>
#include <stdlib.h>
typedef struct TreeNode {
int data;
int height;
struct TreeNode* left;
struct TreeNode* right;
} TreeNode;
/* 获取高度 */
int getHeight(TreeNode* node) {
return node ? node->height : 0;
}
/* 求最大值 */
int maxHeight(int a, int b) {
return a > b ? a : b;
}
/* RR型调整 */
void rrRotation(TreeNode* node, TreeNode** root) {
TreeNode* temp = node->right;
node->right = temp->left;
temp->left = node;
node->height = maxHeight(getHeight(node->left), getHeight(node->right)) + 1;
temp->height = maxHeight(getHeight(temp->left), getHeight(temp->right)) + 1;
*root = temp;
}
/* LL型调整 */
void llRotation(TreeNode* node, TreeNode** root) {
TreeNode* temp = node->left;
node->left = temp->right;
temp->right = node;
node->height = maxHeight(getHeight(node->left), getHeight(node->right)) + 1;
temp->height = maxHeight(getHeight(temp->left), getHeight(temp->right)) + 1;
*root = temp;
}
/* 插值 */
void avlInsert(TreeNode** T, int data) {
if (*T == NULL) {
*T = (TreeNode*)malloc(sizeof(TreeNode));
(*T)->data = data;
(*T)->height = 0;
(*T)->left = NULL;
(*T)->right = NULL;
}
else if (data < (*T)->data) {
avlInsert(&(*T)->left, data);
int lHeight = getHeight((*T)->left); // 左子树高度
int rHeight = getHeight((*T)->right); // 右子树高度
/* 判断高度差 */
if (lHeight - rHeight == 2) {
if (data < (*T)->left->data) {
/* LL调整 */
llRotation(*T, T);
}
else {
/* LR调整 */
rrRotation((*T)->left, &(*T)->left);
llRotation(*T, T);
}
}
}
else if (data > (*T)->data) {
avlInsert(&(*T)->right, data);
int lHeight = getHeight((*T)->left); // 左子树高度
int rHeight = getHeight((*T)->right); // 右子树高度
/* 判断高度差 */
if (rHeight - lHeight == 2) {
if (data > (*T)->right->data) {
/* RR调整 */
rrRotation(*T, T);
}
else {
/* RL调整 */
llRotation((*T)->right, &(*T)->right);
rrRotation(*T, T);
}
}
}
(*T)->height = maxHeight(getHeight((*T)->left), getHeight((*T)->right)) + 1;
}
/* 前序 */
void preOrder(TreeNode* T) {
if (T == NULL) {
return;
}
else {
printf("%d ", T->data);
preOrder(T->left);
preOrder(T->right);
}
}
int main(int argc, char* argv[]) {
TreeNode* T = NULL;
int nums[5] = { 1,2,3,4,5 };
for (int i = 0; i < 5; i++) {
avlInsert(&T, nums[i]);
}
preOrder(T);
printf("\r\n");
return 0;
}
5.9 哈夫曼树
5.9.1 理论基础
1. 什么是哈夫曼树?
树的节点赋值,这个值为权值。
带权路径长度: 边数 X 权值。
哈夫曼树保证,所有叶子节点的带权路径长度最小。
此图构建的是一棵哈夫曼树。
2. 如何通过权值节点列表生成哈夫曼树?(相对于AVL树,哈夫曼树还是比较简单哈)
- 从节点列表中选出 第一小和第二小的节点,并组成一棵树,父亲节点的权值 = 两节点权值之和;
- 将生成的新树再次放入节点列表中,重复第一步,直到列表中 只剩下一个节点。
哈夫曼树实现了一个特点: 权值越大的节点,路径越短。
5.9.2 代码实现
总结: 给我叶子节点序列,然后去构建出一棵哈夫曼树,也称之为最优二叉树。
实现的功能:
- 要使用指定权值的节点列表生成哈夫曼树;
- 遍历哈夫曼树;
- 取节点序列中第一小和第二小的节点;
- 想一想树的存储结构(建议用顺序存储结构)
代码实现:
#include <stdio.h>
#include <stdlib.h>
typedef struct TreeNode {
int weight;
int parent;
int left;
int right;
} TreeNode;
typedef struct HFTree {
TreeNode* data;
int length;
} HFTree;
/* 初始化 */
HFTree* initTree(int* weight, int length) {
HFTree* T = (HFTree*)malloc(sizeof(HFTree));
T->data = (TreeNode*)malloc(sizeof(TreeNode) * (2 * length - 1));
T->length = length;
for (int i = 0; i < length; i++) {
T->data[i].weight = weight[i];
T->data[i].parent = 0;
T->data[i].left = -1;
T->data[i].right = -1;
}
return T;
}
/* 寻找最小值与次小值 */
int* selectMin(HFTree* T) {
int min = 10000;
int minIndex = 0;
int secondMin = 10000;
int secondIndex = 0;
for (int i = 0; i < T->length; i++) {
if (T->data[i].parent == 0) {
if (T->data[i].weight < min) {
min = T->data[i].weight;
minIndex = i;
}
}
}
for (int i = 0; i < T->length; i++) {
if (T->data[i].parent == 0 && i != minIndex) {
if (T->data[i].weight < secondMin) {
secondMin = T->data[i].weight;
secondIndex = i;
}
}
}
int* res = (int*)malloc(sizeof(int) * 2);
res[0] = minIndex;
res[1] = secondIndex;
return res;
}
/* 构建哈夫曼树 */
void createTree(HFTree* T) {
int* res;
int minIndex;
int secondIndex;
int length = T->length * 2 - 1;
for (int i = T->length; i < length; i++) {
res = selectMin(T);
minIndex = res[0];
secondIndex = res[1];
/* 构建父子节点信息 */
T->data[i].weight = T->data[minIndex].weight + T->data[secondIndex].weight;
T->data[i].left = minIndex;
T->data[i].right = secondIndex;
T->data[i].parent = 0;
T->data[minIndex].parent = i;
T->data[secondIndex].parent = i;
T->length++;
}
}
/* 前序 */
void preOrder(HFTree* T, int index) {
if (index != -1) {
printf("%d ", T->data[index].weight);
preOrder(T, T->data[index].left);
preOrder(T, T->data[index].right);
}
}
int main(int argc, char* argv[]) {
int weight[4] = { 1, 2, 3, 4 };
HFTree* T = initTree(weight, 4);
createTree(T);
preOrder(T, T->length - 1); // 最后一个为哈夫曼树的根节点
printf("\r\n");
return 0;
}
6. 图
6.1 图的创建与遍历
图的遍历: BFS和DFS。
代码实现:
#include <stdio.h>
#include <stdlib.h>
#define MAX_SIZE 5
typedef struct Graph {
char* vexs; // 顶点
int** arcs; // 邻接矩阵
int vexNum; // 顶点数
int arcNum; // 边数
} Graph;
typedef struct Queue {
int front;
int rear;
int data[MAX_SIZE];
} Queue;
/* 初始化 */
Queue* initQueue() {
Queue* Q = (Queue*)malloc(sizeof(Queue));
Q->front = Q->rear = 0;
return Q;
}
/* 判断队满 */
int isFull(Queue* Q) {
if ((Q->rear + 1) % MAX_SIZE == Q->rear) {
return 1;
}
else {
return 0;
}
}
/* 判断队空 */
int isEmpty(Queue* Q) {
if (Q->front == Q->rear) {
return 1;
}
else {
return 0;
}
}
/* 入队 */
int push_back(Queue* Q, int data) {
if (isFull(Q)) {
return -1;
}
else {
Q->data[Q->rear] = data;
Q->rear = (Q->rear + 1) % MAX_SIZE;
return data;
}
}
/* 出队 */
int pop_back(Queue* Q) {
if (isEmpty(Q)) {
return -1;
}
else {
int data = Q->data[Q->front];
Q->front = (Q->front + 1) % MAX_SIZE;
return data;
}
}
/* 初始化 */
Graph* initGraph(int vexNum) {
Graph* G = (Graph*)malloc(sizeof(Graph));
G->vexs = (char*)malloc(sizeof(char) * vexNum);
G->arcs = (int**)malloc(sizeof(int*) * vexNum);
for (int i = 0; i < vexNum; i++) {
G->arcs[i] = (int*)malloc(sizeof(int) * vexNum);
}
G->vexNum = vexNum;
G->arcNum = 0;
return G;
}
/* 构建图 */
void createGraph(Graph* G, char* vexs, int* arcs) {
for (int i = 0; i < G->vexNum; i++) {
G->vexs[i] = vexs[i];
for (int j = 0; j < G->vexNum; j++) {
G->arcs[i][j] = *(arcs + i * G->vexNum + j);
if (G->arcs[i][j] != 0) {
G->arcNum++;
}
}
}
G->arcNum /= 2;
}
/* 深度优先遍历 */
void DFS(Graph* G, int* visited, int index) {
printf("%c\t", G->vexs[index]);
visited[index] = 1;
for (int i = 0; i < G->vexNum; i++) {
if (G->arcs[index][i] == 1 && !visited[i]) {
DFS(G, visited, i);
}
}
}
/* 广度优先遍历 */
void BFS(Graph* G, int* visited, int index) {
Queue* Q = initQueue();
printf("%c\t", G->vexs[index]);
visited[index] = 1;
push_back(Q, index);
while (!isEmpty(Q)) {
int i = pop_back(Q);
for (int j = 0; j < G->vexNum; j++) {
if (G->arcs[i][j] == 1 && !visited[j]) {
printf("%c\t", G->vexs[j]);
visited[j] = 1;
push_back(Q, j);
}
}
}
}
int main(int argc, char* argv[]) {
Graph* G = initGraph(5);
int* visited = (int*)malloc(sizeof(int) * G->vexNum);
for (int i = 0; i < G->vexNum; i++) {
visited[i] = 0;
}
int arcs[5][5] = {
0,1,1,1,0,
1,0,1,1,1,
1,1,0,0,0,
1,1,0,0,1,
0,1,0,1,0
};
createGraph(G, "ABCDE", (int*)arcs);
DFS(G, visited, 0);
printf("\r\n");
for (int i = 0; i < G->vexNum; i++) {
visited[i] = 0;
}
BFS(G, visited, 1);
return 0;
}
6.2 最小生成树
应用场景:
比如在通信网络中,我们需要在不同城市之间铺设通信链路,每个城市之间的距离不同,此场景抽象成一个图,然后每个顶点就是不同的城市,边就是城市之间是否连通,边的权值为距离等相关因素,接着我们要让成本最小化,我们就可以用prim算法去生成一个最小生成树。
6.2.1 prim(普里姆)算法
如何实现prim算法?
- 记录当前U集合的状态
- 选择最小边以及将顶点加入到U集合中
代码实现:
#include <stdio.h>
#include <stdlib.h>
/**
* 图顶点之间不通,那么邻接矩阵的值为 MAX
* 如果顶点是自己本身,那么值为 0
*/
#define MAX 32767
typedef struct Graph {
char* vexs; // 顶点
int** arcs; // 邻接矩阵
int vexNum; // 顶点数
int arcNum; // 边数
} Graph;
typedef struct Edge {
char vex;
int weight;
} Edge;
/**
* 初始化边
* 当 Edge.weight = 0 时,代表顶点已经加入到 U 集合中
*/
Edge* initEdge(Graph* G, int index) {
Edge* edge = (Edge*)malloc(sizeof(Edge) * G->vexNum);
for (int i = 0; i < G->vexNum; i++) {
edge[i].vex = G->vexs[index];
edge[i].weight = G->arcs[index][i];
}
return edge;
}
/* 获取最小边 */
int getMinEdge(Edge* edge, Graph* G) {
int min = MAX; // 最小权值
int index; // 最小权值边的索引
for (int i = 0; i < G->vexNum; i++) {
if (edge[i].weight != 0 && min > edge[i].weight) {
min = edge[i].weight;
index = i;
}
}
return index;
}
/* prim算法 */
void prim(Graph* G, int index) {
int minIndex;
Edge* edge = initEdge(G, index);
for (int i = 0; i < G->vexNum - 1; i++) {
minIndex = getMinEdge(edge, G);
printf("v%c -> v%c, weight = %d\r\n", edge[minIndex].vex, G->vexs[minIndex], edge[minIndex].weight);
edge[minIndex].weight = 0;
for (int j = 0; j < G->vexNum; j++) {
if (G->arcs[minIndex][j] < edge[j].weight) { // 更新边集 -> Edge
edge[j].weight = G->arcs[minIndex][j];
edge[j].vex = G->vexs[minIndex];
}
}
}
}
/* 初始化图 */
Graph* initGraph(int vexNum) {
Graph* G = (Graph*)malloc(sizeof(Graph));
G->vexs = (char*)malloc(sizeof(char) * vexNum);
G->arcs = (int**)malloc(sizeof(int*) * vexNum);
for (int i = 0; i < vexNum; i++) {
G->arcs[i] = (int*)malloc(sizeof(int) * vexNum);
}
G->vexNum = vexNum;
G->arcNum = 0;
return G;
}
/* 构建图 */
void createGraph(Graph* G, char* vexs, int* arcs) {
for (int i = 0; i < G->vexNum; i++) {
G->vexs[i] = vexs[i];
for (int j = 0; j < G->vexNum; j++) {
G->arcs[i][j] = *(arcs + i * G->vexNum + j);
if (G->arcs[i][j] != 0 && G->arcs[i][j] != MAX) {
G->arcNum++;
}
}
}
G->arcNum /= 2;
}
/* 深度优先遍历 */
void DFS(Graph* G, int* visited, int index) {
printf("%c\t", G->vexs[index]);
visited[index] = 1;
for (int i = 0; i < G->vexNum; i++) {
if (G->arcs[index][i] > 0 && G->arcs[index][i] != MAX && !visited[i]) {
DFS(G, visited, i);
}
}
}
int main(int argc, char* argv[]) {
Graph* G = initGraph(6);
int* visited = (int*)malloc(sizeof(int) * G->vexNum);
for (int i = 0; i < G->vexNum; i++) {
visited[i] = 0;
}
int arcs[6][6] = {
0, 6, 1, 5, MAX, MAX,
6, 0, 5, MAX, 3, MAX,
1, 5, 0, 5, 6, 4,
5, MAX, 5, 0, MAX, 2,
MAX, 3, 6, MAX, 0, 6,
MAX, MAX, 4, 2, 6, 0
};
createGraph(G, "123456", (int*)arcs);
DFS(G, visited, 0);
printf("\r\n");
prim(G, 0);
return 0;
}
6.2.2 kruskal(克鲁斯卡尔)算法
克鲁斯卡尔找边法:
- 根据大小对边排序 1 2 3 4 5 5 5 6 6 6
- 选边
代码实现:
维护一个边的数组并排序;
判断图是否连通?(难点)
需要一个辅助数组,来记录当前索引的节点所属于哪个连通分量。
#include <stdio.h>
#include <stdlib.h>
#define MAX 32767
typedef struct Graph {
char* vexs; // 顶点
int** arcs; // 邻接矩阵
int vexNum; // 顶点数
int arcNum; // 边数
} Graph;
typedef struct Edge{
int start;
int end;
int weight;
} Edge;
/* 初始化边 */
Edge* initEdge(Graph* G) {
int index = 0;
Edge* edge = (Edge*)malloc(sizeof(Edge) * G->arcNum);
for (int i = 0; i < G->vexNum; i++) {
for (int j = i + 1; j < G->vexNum; j++) {
if (G->arcs[i][j] != MAX) {
edge[index].start = i;
edge[index].end = j;
edge[index].weight = G->arcs[i][j];
index++;
}
}
}
return edge;
}
/* 边排序(冒泡排序)*/
void sortEdge(Edge* edge, Graph* G) {
Edge temp;
for (int i = 0; i < G->arcNum - 1; i++) {
for (int j = 0; j < G->arcNum - i - 1; j++) {
if (edge[j].weight > edge[j + 1].weight) {
temp = edge[j];
edge[j] = edge[j + 1];
edge[j + 1] = temp;
}
}
}
}
/* kruskal算法 */
void kruskal(Graph* G) {
int* connected = (int*)malloc(sizeof(int) * G->vexNum); // 连通量标识数组
for (int i = 0; i < G->vexNum; i++) {
connected[i] = i;
}
Edge* edge = initEdge(G);
sortEdge(edge, G);
for (int i = 0; i < G->arcNum; i++) {
int start = connected[edge[i].start];
int end = connected[edge[i].end];
if (start != end) {
printf("v%c -> v%c, weight = %d\r\n", G->vexs[edge[i].start], G->vexs[edge[i].end], edge[i].weight);
for (int j = 0; j < G->vexNum; j++) {
if (connected[j] == end)
connected[j] = start;
}
}
}
}
/* 初始化图 */
Graph* initGraph(int vexNum) {
Graph* G = (Graph*)malloc(sizeof(Graph));
G->vexs = (char*)malloc(sizeof(char) * vexNum);
G->arcs = (int**)malloc(sizeof(int*) * vexNum);
for (int i = 0; i < vexNum; i++) {
G->arcs[i] = (int*)malloc(sizeof(int) * vexNum);
}
G->vexNum = vexNum;
G->arcNum = 0;
return G;
}
/* 构建图 */
void createGraph(Graph* G, char* vexs, int* arcs) {
for (int i = 0; i < G->vexNum; i++) {
G->vexs[i] = vexs[i];
for (int j = 0; j < G->vexNum; j++) {
G->arcs[i][j] = *(arcs + i * G->vexNum + j);
if (G->arcs[i][j] != 0 && G->arcs[i][j] != MAX) {
G->arcNum++;
}
}
}
G->arcNum /= 2;
}
/* 深度优先遍历 */
void DFS(Graph* G, int* visited, int index) {
printf("%c\t", G->vexs[index]);
visited[index] = 1;
for (int i = 0; i < G->vexNum; i++) {
if (G->arcs[index][i] > 0 && G->arcs[index][i] != MAX && !visited[i]) {
DFS(G, visited, i);
}
}
}
int main(int argc, char* argv[]) {
Graph* G = initGraph(6);
int* visited = (int*)malloc(sizeof(int) * G->vexNum);
for (int i = 0; i < G->vexNum; i++) {
visited[i] = 0;
}
int arcs[6][6] = {
0, 6, 1, 5, MAX, MAX,
6, 0, 5, MAX, 3, MAX,
1, 5, 0, 5, 6, 4,
5, MAX, 5, 0, MAX, 2,
MAX, 3, 6, MAX, 0, 6,
MAX, MAX, 4, 2, 6, 0
};
createGraph(G, "123456", (int*)arcs);
DFS(G, visited, 0);
printf("\r\n");
kruskal(G);
return 0;
}
6.3 最短路径算法
6.3.1 dijkstra(迪杰斯特拉算法)
6.3.1.1 理论基础
S数组: 记录了目标顶点到其它顶点的最短路径是否求得;
P数组: 记录了目标节点到其它顶点的最短路径的前驱节点;
D数组: 记录目标顶点到其它顶点的最短路径的长度。
6.3.1.2 代码实现
#include <stdio.h>
#include <stdlib.h>
#define MAX 32767
typedef struct Graph {
char* vexs; // 顶点
int** arcs; // 邻接矩阵
int vexNum; // 顶点数
int arcNum; // 边数
} Graph;
/* 初始化 */
Graph* initGraph(int vexNum) {
Graph* G = (Graph*)malloc(sizeof(Graph));
G->vexs = (char*)malloc(sizeof(char) * vexNum);
G->arcs = (int**)malloc(sizeof(int*) * vexNum);
for (int i = 0; i < vexNum; i++) {
G->arcs[i] = (int*)malloc(sizeof(int) * vexNum);
}
G->vexNum = vexNum;
G->arcNum = 0;
return G;
}
/* 构建图 */
void createGraph(Graph* G, char* vexs, int* arcs) {
for (int i = 0; i < G->vexNum; i++) {
G->vexs[i] = vexs[i];
for (int j = 0; j < G->vexNum; j++) {
G->arcs[i][j] = *(arcs + i * G->vexNum + j);
if (G->arcs[i][j] != 0 && G->arcs[i][j] != MAX) {
G->arcNum++;
}
}
}
G->arcNum /= 2;
}
/* 深度优先遍历DFS */
void DFS(Graph* G, int* visited, int index) {
printf("%c\t", G->vexs[index]);
visited[index] = 1;
for (int i = 0; i < G->vexNum; i++) {
if (G->arcs[index][i] > 0 && G->arcs[index][i] != MAX && !visited[i]) {
DFS(G, visited, i);
}
}
}
/* 获取最小边 */
int getMin(int* s, int* d, Graph* G) {
int index;
int min = MAX;
for (int i = 0; i < G->vexNum; i++) {
if (!s[i] && d[i] < min) {
min = d[i];
index = i;
}
}
return index;
}
/* 迪杰斯特拉算法 */
void dijkstra(Graph* G, int index) {
/* 创建辅助数组 */
int* s = (int*)malloc(sizeof(int) * G->vexNum);
int* p = (int*)malloc(sizeof(int) * G->vexNum);
int* d = (int*)malloc(sizeof(int) * G->vexNum);
/* 初始化辅助数组 */
for (int i = 0; i < G->vexNum; i++) {
if (G->arcs[index][i] > 0 && G->arcs[index][i] != MAX) {
p[i] = index;
d[i] = G->arcs[index][i];
}
else {
p[i] = -1;
d[i] = MAX;
}
if (i == index) {
s[i] = 1;
d[i] = 0;
}
else
s[i] = 0;
}
/* 更新数组,寻找最短距离 */
for (int i = 0; i < G->vexNum - 1; i++) {
int index = getMin(s, d, G);
s[index] = 1;
for (int j = 0; j < G->vexNum; j++) {
if (!s[j] && d[index] + G->arcs[index][j] < d[j] ) {
d[j] = d[index] + G->arcs[index][j];
p[j] = index;
}
}
}
/* 打印输出 */
for (int i = 0; i < G->vexNum; i++) {
printf("%d %d %d\n", s[i], p[i], d[i]);
}
}
int main(int argc, char* argv[]) {
Graph* G = initGraph(7);
int* visited = (int*)malloc(sizeof(int) * G->vexNum);
for (int i = 0; i < G->vexNum; i++) {
visited[i] = 0;
}
int arcs[7][7] = {
0, 12, MAX, MAX, MAX, 16, 14,
12, 0, 10, MAX, MAX, 7, MAX,
MAX, 10, 0, 3, 5, 6, MAX,
MAX, MAX, 3, 0, 4, MAX, MAX,
MAX, MAX, 5, 4, 0, 2, 8,
16, 7, 6, MAX, 2, 0, 9,
14, MAX, MAX, MAX, 8, 9, 0
};
createGraph(G, "1234567", (int*)arcs);
DFS(G, visited, 0);
printf("\r\n");
dijkstra(G, 0);
return 0;
}
6.3.2 弗洛伊德算法(floyd)
6.3.2.1 理论
迪杰斯特拉算法是以一个顶点为起点,此起点到其它顶点的最短路径,而弗洛伊德算法是求的每一对顶点之间的最短距离。
p数组:保存了两点之间最短路径的前驱
d数组:保存了两点之间的最短路径长度
弗洛伊德算法核心:试探法,通过加入不同的点进行中转,选出最优解。
6.3.2.2 代码实现
#include <stdio.h>
#include <stdlib.h>
#define MAX 32767
typedef struct Graph {
char* vexs; // 顶点
int** arcs; // 邻接矩阵
int vexNum; // 顶点数
int arcNum; // 边数
} Graph;
/* 初始化 */
Graph* initGraph(int vexNum) {
Graph* G = (Graph*)malloc(sizeof(Graph));
G->vexs = (char*)malloc(sizeof(char) * vexNum);
G->arcs = (int**)malloc(sizeof(int*) * vexNum);
for (int i = 0; i < vexNum; i++) {
G->arcs[i] = (int*)malloc(sizeof(int) * vexNum);
}
G->vexNum = vexNum;
G->arcNum = 0;
return G;
}
/* 构建图 */
void createGraph(Graph* G, char* vexs, int* arcs) {
for (int i = 0; i < G->vexNum; i++) {
G->vexs[i] = vexs[i];
for (int j = 0; j < G->vexNum; j++) {
G->arcs[i][j] = *(arcs + i * G->vexNum + j);
if (G->arcs[i][j] != 0 && G->arcs[i][j] != MAX) {
G->arcNum++;
}
}
}
G->arcNum /= 2;
}
/* 深度优先遍历DFS */
void DFS(Graph* G, int* visited, int index) {
printf("%c\t", G->vexs[index]);
visited[index] = 1;
for (int i = 0; i < G->vexNum; i++) {
if (G->arcs[index][i] > 0 && G->arcs[index][i] != MAX && !visited[i]) {
DFS(G, visited, i);
}
}
}
/* 弗洛伊德算法 */
void floyd(Graph* G) {
/* 初始化p和d数组*/
int** p = (int**)malloc(sizeof(int*) * G->vexNum);
for (int i = 0; i < G->vexNum; i++) {
*(p + i) = (int*)malloc(sizeof(int) * G->vexNum);
}
int** d = (int**)malloc(sizeof(int*) * G->vexNum);
for (int i = 0; i < G->vexNum; i++) {
*(d + i) = (int*)malloc(sizeof(int) * G->vexNum);
}
for (int i = 0; i < G->vexNum; i++) {
for (int j = 0; j < G->vexNum; j++) {
d[i][j] = G->arcs[i][j];
if (G->arcs[i][j] > 0 && G->arcs[i][j] != MAX) {
p[i][j] = i;
}
else {
p[i][j] = -1;
}
}
}
/* 更新p和d数组 */
for (int i = 0; i < G->vexNum; i++) {
for (int j = 0; j < G->vexNum; j++) {
for (int k = 0; k < G->vexNum; k++) {
if (d[j][i] + d[i][k] < d[j][k]) {
d[j][k] = d[j][i] + d[i][k];
p[j][k] = p[i][k];
}
}
}
}
/* 输出p和d矩阵 */
for (int i = 0; i < G->vexNum; i++) {
for (int j = 0; j < G->vexNum; j++) {
printf("%d ", d[i][j]);
}
printf("\r\n");
}
for (int i = 0; i < G->vexNum; i++) {
for (int j = 0; j < G->vexNum; j++) {
printf("%d ", p[i][j]);
}
printf("\r\n");
}
/* 内存释放 */
for (int i = 0; i < G->vexNum; i++) {
free(p[i]);
free(d[i]);
}
free(p);
free(d);
}
int main(int argc, char* argv[]) {
Graph* G = initGraph(4);
int* visited = (int*)malloc(sizeof(int) * G->vexNum);
for (int i = 0; i < G->vexNum; i++) {
visited[i] = 0;
}
int arcs[4][4] = {
0, 1, MAX, 3,
1, 0, 2, 2,
MAX, 2, 0, 8,
3, 2, 8, 0
};
createGraph(G, "1234", (int*)arcs);
DFS(G, visited, 0);
printf("\r\n");
floyd(G);
return 0;
}