文章目录
排序算法
冒泡排序
-
冒泡排序
思想:两数比较 将最大 或 最小 的元素,放到最后。
int i, j, temp; // 定义 外层循环变量 i, // i 外层循环变量,j 内层循环变量 ,temp 临时变量 for( i=0 ; i<len-1 ; i++ ){ // 外层 控制 元素 比较多少躺 for( j=0 ; j<len-i-1 ; j++ ){ //内层 控制 元素 比较次数 if( arr[j] > arr[j+1] ){ //从小到大 temp = arr[j]; arr[j] = arr[j+1]; arr[j+1] = temp; } } }
-
顺序表 下的 冒泡排序
typedef int keyType; //关键字 typedef struct{ KeyType key; }RecType; //单个节点 的 数据类型 typedef struct{ Rectype r[10000]; int len; //顺序表长度 }Sqlist; //顺序表 的 数据类型 int i, j, temp; Sqlist *q; for(i=0 ; i< q->len-1 ; i++){ for(j=0 ; j< q->len-i-1 ;j++){ if( q->r[j].key >q->r[j+1].key ){ temp = q->r[j].key; q->r[j].key = q->r[j+1].key; q->r[j+1].key = temp; } } }
注意:冒泡排序 稳定,最多执行 n(n-1)/2 次。
选择排序
-
简单选择排序
思想:找 最大 或 最小 的元素,往前放,分成 "已排序" 和 "待排序" 序列。
void selectSort(int arr[], int len){ int i, j ,temp, min; for(i=0 ; i< len ; i++){ min = i; //min 标记 未排序第一个元素的位置 for(j=i+1; j< len ; j++){ //找 比 min位置 还小的元素 if(arr[j] < arr[min]){ min=j; } } if(min != i){ temp = arr[min]; arr[min] = arr[i]; arr[i] = temp; } } }
选择排序 不稳定,平均比较次数 n(n-1)/2 次。
插入排序
-
直接插入排序
思想:由后向前 查找 确定的位置,并插入元素。
void insertSort(int arr, int len){ int i, j, temp; for( i=1; i < len ; i++ ){ // 从1开始,0位置当作有序序列 temp = arr[i]; // temp记录未排序的第一个元素 for(j=i-1; j>=0 && arr[j]>temp ;j--){ arr[j+1] = arr[j]; // 元素后移,为 待插入元素 腾空位置 arr[j+1] = temp; // 插入 比较的后一位 } }
直接插入排序,是在有序基础上,速度最快且最稳定的排序方法,最多执行 n(n-1)/2 次。
希尔排序
-
希尔排序
希尔排序:又称为 "缩小增量排序"。 思想:按照 步长 进行分组,然后每一组的 第 几个 元素 进行排序。 增量 及 步长。
// dk 是 增量步长 void shellInsert(int arr[], int len, int dk){ int i, j, temp; // i 循环变量,j 前面组的位置,temp 临时变量 for( i=dk; i<len; i++ ){ //判断每组的 第几个元素大小 if( arr[i] < arr[i-dk] ){ //后面组 小于 前面组 temp = arr[i]; j = i-dk; for( ; j>=0 && arr[j]>temp ; j-=dk ){ //前面的值 给到 后面 arr[j+dk] = arr[j]; } arr[j+dk] = tmep; } } }
希尔排序 是 不稳定的。
快速排序
-
快速排序
基准值,i 从前向后找,比基准值大的元素,j 从后向前找,找比基准值小的元素。 最后 结束的条件是 i = j ,此时 将 基准值 放到这个位置。
int getPivot(int arr[], int low, int high){ int pivot = arr[low]; while( low < high ){ // i < j 的时候 // j 从后向前找,比基准值小的元素 while( low < high && arr[high] >= pivot ){ high--; } // 找到了 比基准值小的元素,将 元素 给到 i 位置 arr[low] = arr[high]; // i 从前向后找,找比 基准值 大 的元素 while( low < high && arr[low] <= pivot ){ low++; } arr[high] = arr[low]; } // 找到了 比基准值大的元素,将 元素 给到 j 位置 arr[low] = pivot; //返回基准值位置 return low; } void quickSort(int arr[], int low, int high){ if(low < high){ //基准值位置的变量 int pivot = getPivot(arr, low, high); //递归 对 基准值位置 左边 进行快速排序 quickSort(arr, low, pivot-1); //递归 对 基准值位置 右边 进行快速排序 quickSort(arr, pivot+1, high); } } int main(){ int arr[]={4,2,7,5,9}; }
查找算法
顺序查找
-
顺序查找
遍历数组,拿元素一次比较,相同 返回 下标,找不到 则 返回 -1。
// arr数组, len 长度, value 待查找值 int linearSearch(int arr[], int len, int vaule){ for(int i = 0; i < len; i++){ if( arr[i] == value ){ return i; //返回下标 return i+1; //返回 第几位 } } return -1; //找不到 }
二分查找
-
二分查找(非递归)
// arr 数组, low 左值, high 右值, key 待查找数据 int binarySearch(int arr[], int low, int high, int key){ int mid; //中间值的下标 while( low <= high ){ mid = (low+high)/2; //找中间位置 if( key == arr[mid] ){ return mid; //返回下标 } else if( key > arr[mid] ){ low = mid + 1; //key 大于中间值,头移动至中间值后一位 } else { high = low - 1; //key 小于中间值,尾移动至中间值前一位 } } //没有找到待查找数据 return -1; }
-
二分查找(递归)
int binarySearch(int arr[], int low, int high, int key){ int mid; //中间值的下标 if( low <= high ){ mid = (low+high)/2; if( key == arr[mid] ){ return mid; //递归出口 } else if( key >= arr[mid] ){ return binarySearch(arr, mid+1, high); }else { return binarySearch(arr, low, mid-1); } } return -1; }
顺序表
-
线性表
1、顺序表(数组) // 顺序表 一般 占用一片连续的存储空间 2、链表 //链表,结点 再内存中 随机分配,指针链接起来即可
数组 链表 查询 快 慢 增删 慢 快 可以通过索引来直接访问指定位置,
增删元素需要移动大量元素。只能通过指针指向下一位节点进行查询比较,
增删元素不需要移动元素。遇到数组时,注意:下标 从 0 开始,还是 从 1 开始。
数组名 是 首元素地址,也是 指针。
bit 位 0100 0110 byte 字节 1 byte = 8 bit int arr[] = {1,2,3,4,5}; int *p = arr; // 数组 首元素地址 arr == &arr[0] == p
数组
-
输入元素
void input(int arr[], int len){ //定义循环变量 int i; int *p = arr; for(i=0; i<len; i++){ printf("请输入第%d个元素:",i+1); scanf("%d",&arr[i]); // scanf("%d",arr + i); // scanf("%d",p + i); } }
-
输出元素(遍历数组)
void output(int arr[], int len){ //定义循环变量 int i; int *p = arr; for(i=0; i<len; i++){ printf("%d ",arr[i]); // printf("%d ", *(arr + i) ); // printf("%d ", *(p + i) ); } }
-
查询元素
//根据 元素值 寻找 元素下标 int searchVlue(int arr[], int len, int value){ //定义循环变量 int i; for(i=0; i<len ;i++){ if(value == arr[i]){ return i; } } return -1; }
//根据 元素位置 查找 元素值 int searchKey(int arr[], int len ,int key){ //判断 元素下标 是否 在数组范围之内 if( key<0 || key>len-1 ){ printf("元素位置错误!"); return; // exit(0); 结束程序 } printf("查找的元素是:%d\n",arr[key]); }
-
动态生成数组
int * getArray(int num){ // 返回一个指针变量 int i,*p; // p 指向 动态数组的首地址 p = (int *)malloc(sizeof(int)*num); if(!p){ printf("空间申请失败!"); return; } return p; }
-
修改元素
//根据 元素值 修改 元素值 void updateValue(int arr[], int len){ // 修改前,修改后 int firstValue, lastValue; printf("请输入你要修改的元素:"); scanf("%d",&firstValue); printf("请输入你要修改后的元素:"); scanf("%d",&lastValue); for(int i=0; i<len; i++){ if(arr[i] == firstValue){ arr[i] = lastValue; } } }
//根据 元素位置 修改 元素值 void uodateKey(int arr[], int len){ int key, value; printf("请输入你要修改的位置:"); scanf("%d",&key); printf("请输入你要修改后的值:"); scanf("%d",&value); if( key<0 || key >len-1 ){ printf("位置不对!"); reutnr; } arr[key] = value; }
-
插入元素
void insert(int arr[], int *len, int index, int value){ int i; if(index < 0 || index > *len - 1){ return; } //元素后移 for(i=len-1; i>=index; i++){ arr[i+1] = arr[i]; } //插入元素 arr[index] = value; //元素插入 长度增加 (*len)++; }
-
删除元素
void delete(int arr[],int *len,int index){ int i; if(index < 0 || index > *len - 1){ return; } //元素前移 for(i=index; i<len-1; i++){ arr[i] = arr[i+1]; } //元素删除 长度减少 (*len)--; }
-
字符数组
// 1、大括号初始化字符数组时,需要预留一个空间 做 结束标志 \0,否则会乱码 char arr[5] = {'h','e','l','l','o'}; //乱码 // 2、使用双引初始化时,自带结束标志 char str[] = "hello";
strlen 求字符串长度时,不带 \0
sizeof 求字符串长度时,带 \0
-
字符数组 两种 键盘录入方式
1、gets( 字符串 ); 2、scanf("%f", 字符串 );
// 可以校验 是否越界,stdio 是标准输入流(键盘输入) fgets(a, sizeof(a), stfio);
链表
-
结点
typedef struct Node{ int data; //数据域 struct Node *next; //指针域(存放下一个结点的位置) }Node;
-
结点类型
1、头结点 // 只有指针域,数据与不存放数据,一般用 head 表示 2、首元结点 // 第一个存放 数据的 结点
有头结点:方便使用 头插法 插入元素。
-
链表的初始化(只生成头结点)
typedef struct Node{ int data; //数据域 struct Node *next; //指针域 }Node,*LinkeList; // Node struct Node 的别名 LinkList struct Node * LinkList create(){ //动态申请空间(head 指针指向这个空间) LinkList head = (Node *)malloc(sizeof(Node)); //判断空间是否开辟成功 if(!head){ printf("空间申请失败!\n"); return NULL; } //头节点,指针域值为 NULL head->next = NULL; //返回头结点 指针 return head; }
-
链表长度获取
// 实际传的是 头结点的地址,但是代表的是 一个链表 int getSize(LinkList head){ //头结点 为空 if(!head){ return 0; } // p 指向首元结点 Node *p = head->next; // 等价于 LinkList q = head->next; //计数器 int count = 0; while(p){ count++; p = p->next; //指针后移 } //返回链表长度 return count; }
-
链表的遍历(数据域)
void foreach(LinkList head){ if(!head){ return; } LinkList p = head->next; while(p){ printf("%d ",p->data); p = p->next; } }
-
链表的清空
void clear(LinkList head){ if(!head){ return; } Node *p = head->next; while(p){ // q 存放的是,p 下一个结点的地址 LinkList q = p->next; // 释放 p free(p); p = q; } head->next = NULL; }
-
链表的销毁
void destory(LinkList head){ if(!head){ return; } clear(head); free(head); // 释放头结点 head = NULL; // 指针变量 设置为 空 }
-
链表的插入(尾插法)
void insert(LinkList head){ if(!head){ return; } int num; printf("请输入要插入的结点数量:"); scanf("%d",&num); Node *q = head; for(int i=0; i<num; i++){ LinkList p = (Node *)malloc(sizeof(Node)); printf("请输入数据域的值:"); scanf("%d",&p->data); q->next = p; q = q->next; } q->next = NULL; }
尾插法 生成 链表。
-
链表的插入(头插法)
void insert(LinkList head){ if(!head){ return; } int num; printf("请输入要插入的结点数量:"); scanf("%d",&num); for(int i=0; i<num; i++){ Node *p = (Node *)malloc(sizeof(Node)); printf("请输入数据域的值:"); scanf("%d",&p->data); p->next = NULL; // 头插法 p->next = head->next; head->next = p; } }
头插法,经常用 头指针 及逆行元素的插入。
-
链表的删除
// 根据 元素值 去删除 这个结点 void delete(LinkList head, int value){ if(!head){ return; } Node *p = head; // p 指针 指向 头结点的下一个 Node *q; while( p->next ){ if( p->next->data == value ){ // 若满足条件,则 p 就是待删除元素结点的直接前驱 q = p->next; // 令 q 指向 待删除结点 p->next = q->next; // 直接前驱结点 指向 待删除结点的 后一位结点 free(q); // 此时 释放结点 } else{ p = p->next; } } }
删除一个结点,必须知道这个结点的 直接前驱。
栈(stack)
-
栈的特点:先进后出 / 后进先出
1、栈 分成了 "栈顶" 与 "栈底" (在栈顶进行元素的插入和删除) 2、入栈(push) 出栈(pop)
-
栈的顺序存储(使用 数组)
数组的首地址 做 "栈顶" 还是 "栈底" 比较好? 答:做 "栈底" 比较好。
因为 栈顶 要进行元素的插入和删除操作,所以数组尾部更适合比较频繁的插入和删除。
-
栈 的 结构体
typedef struct Stack{ int data[MXA]; //数据域 (MAX 为 宏定义值) int size; //栈的大小 (指 栈中的元素个数) }Stack, * SeqStack;
-
栈 的 初始化
SeqStack create(){ //创建栈,相当于 动态开辟一个数组出来 SeqStack p = (Stack *)malloc(sizeof(Stack)); if(!p){ printf("空间开辟失败!"); return; } //初始化 栈 的大小 p->size = 0; //初始化 栈中的元素 for(int i=0; i<MAX ;i++){ p->data[i] = 0; } return p; }
-
入栈
void push(SeqStack stack, int data){ if(!stack){ return; } //入栈 需要判断 栈是否已满 if( stack->size == MAX ){ printf("栈已满!\n"); return; } // stack->size 栈的大小 就是 待插入元素 的下一个位置 stack->data[stack->size] = data; //改变栈的大小 stack->size++; }
入栈 需要注意 栈是否 不存在 and 已满。
-
出栈
void pop(SeqStack stack){ if(!stack) return; if(stack->size == 0){ printf("栈已空\n"); return; } //删除一个元素 stack->data[stack->size -1] = 0; //改变栈的大小 stack->size --; }
出栈 需要注意 栈是否 不存在 and 已空。
-
遍历 栈中元素
void forerachStack(SeqStack stack){ if(!stack) return; for(int i=0;i<stack->size; i++) printf("%d ",stack->data[i]); }
-
-
栈 的 链式存储(链表)
链表的头结点端 做 “栈顶” 还是 “栈底” 比较好?
答:做 “栈顶” 比较好。因为 链表的头结点端 可以更方便的进行元素的 插入(头插法) and 删除(头结点做直接前驱)。
-
栈链式存储 结构体
//结点结构体 trpedef struct Node{ int data; struct Node *next; }Node; //栈的结构体 trpedef struct Stack{ struct Node header; //头结点 int size; //栈的大小 即元素个数 }Stack, *LinkStack;
-
初始化栈
LinkStack inir_LinkStack(){ // myStack 是指针,指向所申请的空间 LinkStack myStack = (Stack *)malloc(sizeof(Stack)); if(!Stack) return NULL; //初始化 长度为0 myStack->size = 0; //相当于 初始化 链表的头结点 指针域 myStack->header.next = NULL; }
什么时候使用 " -> " ,什么时候使用 " . " ?
-> 在指针的时候
. 在变量名的时候
-
注意事项
struct Node *list; list->next list->next struct Node a; a.data a.next
-
入栈
void push(LinkStack stack, int data){ if(!stack) return; //创建一个结点 Node *p = (Node *)malloc(sizeof(Node)); //给 结点数据域 赋值 p->data = data; //头插法 先连后面 p->next= stack->header.next; stack->header.next = p; //改变 栈中元素 的个数 stack->size ++; }
-
出栈
void pop(LinkStack stack){ //判断栈是否存在 if(!stack) return; //判断栈空 if(stack->size ==0){ printf("栈中无元素!\n"); return; } //定义一个节点类型的指针,用作删除结点 Node *p = stack->header.next; //头结点 连接到 删除结点 的后边 stack->header.next = p->next; //释放结点 free(p); //更新 栈的大小 stack->size --; }
-
队列
-
队列:先进先出 后进后出
1、只允许 从一端 添加元素,从 另一端 删除元素 2、队头 出元素,队尾 进元素 3、push 入队 pop 出队
-
队列的顺序存储
数组的首地址做 “队头” 好,还是 “队尾” 比较好?
答:都一样。做 队头 和 队尾 差别不大,因为都需要移动大量元素。
-
队列的结构体
typedef struct Queue{ int data[MAX]; //顺序存储 数组模拟的队列 int size; //队列的大小(队列中元素的个数) }Queue, *SeqQueue;
-
队列的初始化(数组头做队头,数组尾做队尾)
SeqQueue init(){ //申请队列空间,即数组空间 SeqQueue p = (Queue *)malloc(sizeof(Queue)); if(!p){ return NULL; } //初始化队列大小 p->size = 0; for(int i=0; i<MAX ;i++){ p->data[i] = 0; } reutrn p; }
-
入队
void push(SeqQueue p, int value){ if(!p){ //队列不存在 return; } if(p->size == MAX){ printf("队列元素已满!\n"); return; } //插入元素(进队) p->data[p->size] = value; //改变队列元素个数 p->size ++; }
-
出队
void push(SeqQueue p){ if(!p){ //队列不存在 return; } if(p->size == 0){ printf("队列元素已空!\n"); return; } //出队 删除数组首元素 for(int i=0; i< p->size -1 ;i++){ p->data[i] = p->data[i+1]; } //改变队列元素个数 p->size --; }
要记得,看到循环里边有 i+1 的时候,要改变 最终值。
-
-
队列的链式存储
链表的 头结点端 做 “队头” 还是 “队尾” 比较好?
答:做 “队头” 好。因为 头结点端 进行删除方便,尾端 插入元素方便(尾插法)。
-
队列的结构体
//队列的结构体 typedef struct LQueue{ struct Node header; //头结点 int size; //队列大小 struct Node* pTail; //尾结点指针 }LQueue, *LinkQueue;
-
-
循环队列
//队满 front = (rear + 1)%n; // front 头,rear 尾 //队空 front = rear; // 头指针 指向 尾指针时,队为空
栈的应用
- 前 中 后 缀表达式 如何计算?
首先运算优先级最高的,然后 将 运算符放两个整体之前 就是 前缀,放两个整体之后,就是 后缀。
串
-
零个 或者多个任意字符组成的 有限序列 是我们的字符串。
-
串 是 内容受限的线性表。
-
子串:一个串中 任意个连续字符组成的子序列(含空串)。
"*jkj12k0" //它的子串有哪些? // 单字符 6 // 双字符 7 // 三字符 6 // 四字符 5 // 五字符 4 // 六字符 3 // 七字符 2 // 八字符 1 // 空串 1 分别是: // * // *j // *jk // *jkj // *jkj1 // *jkj12 // *jkj12k // *jkj12k0 // j // jk // jkj // jkj1 // jkj12 // jkj12k // jkj12k0 // k // kj // kj1 // kj12 // kj12k // kj12k0 // j1 // j12 // j12k // j12k0 // 1 // 12 // 12k // 12k0 // 2 // 2k // 2k0 // k0 // 0 // 空串
-
真子串(不包含自身的子串)。
-
主串:包含子串的串,称之为 主串。
-
字符位置:字符在数组中的位置。
-
子串位置:子串第一个字符串 在 主串中的位置。
-
空格串:一个空格或者多个空格组成的串,称之为空格串。
-
空串:零个没有任意字符的串,称之为空串。
" " // 空格串 "" // 空串
-
串相等:当两个串长度相等,并且每个位置对应字符完全一样的时候,两个串才相等。
串的基本操作
1. 赋值 StrAssign(s,t) 将串t的值赋值给串s
2. 求串长 StrLength(s) 返回字符串s的长度
3. 串比较 StrCompare(s,t) 比较两个串,返回-1、0和1,分别代表s<t、s=t和s>t
4. 连接 Concat(s,t) 将串t连接在串s的尾部,形成新的字符串
5. 求子串 SubString(s,start,len) 返回串s从start开始、长度为len的字符序列
6. 串拷贝 StrCopy(t,s) 将串s拷贝到串t上
7. 串判空 StrEmpty(s) 判断串s是否为空串
8. 清空串 ClearString(s) 清空字符串s
9. 子串位置 Index(S,T,pos) 字符串S中返回T所出现的位置
10. 串替换 Replace(S,T) 串S替换为T
-
串的顺序存储
-
结构体
typedef struct SString{ char ch[MAX +1]; // 字符数组空间,MAX 为宏定义值 int length; // 串的长度 };
-
-
BF算法
// str 是主串 subStr 是模式匹配串 int BF(char str[], char subStr[]){ int i=0,j=0; //定义 i 主串下表,j 模式串下标 while( i<strlen(str) && j<strlen(subStr) ){ //如果对应位置相等,则下标后移,比较下一位元素 if(str[i] == subStr[j]){ i++; j++; } else{ i = i-j+1; //主串下标进行回溯 j = 0; //子串下标回溯 } } if( j == strlen(subStr) ){ //子串下标 与 子串长度相等 //返回子串位置 return i - strlen(subStr) + 1; } return -1; //未找到子串下标 }
矩阵
-
把矩阵当作 二维数组,只不过下标都是从 1 开始。
-
二维数组 以行为主序存储,二维数组 以列为主序存储。
以行存储,先去计算前 (i-1)行有多少元素 以列存储,先去计算前 (j-1)列有多少元素
前提:数组下标从一开始。
-
对称矩阵:以主对角线为对称轴,个元素对应相等的矩阵。
-
对角矩阵:除主对角线意外,全是 0 的矩阵。
-
稀疏矩阵:非零元素个数,远远少于 零元素个数,且零元素分布无规律。
-
三元组 (行,列,值) :
(4,5,8) //第四行,第五列,值为8 (前提:行列下标都从1开始) (4,5,8) //第三行,第四行,值为8 (前提:行列下标都从0开始)
树
- 树 是 非线性结构 。
- 根节点:没有 父节点 的节点 。
- 叶子节点:没有 子节点 的节点 。
- 节点的层次:树一共有几层,节点的层次就是几 。
- 节点的祖先:从 根节点 到 该节点 的 所有节点 。
- 节点的度:该节点 有几个 直接后继,就是几度。
- 树的度:树中,最大的 节点度 就称之为 树的度。
- 树的深度/高度:从 根节点 开始,长度最长的一条路径 有几个节点,深度就是几。
-
树的性质
1、树中的节点数 等于 所有节点的度数加1。 2、度为 M 的树中,第i层上至多有 M的(i-1) 次方个节点(i>=1)。 3、高度为 H 的 M 叉树中,之多有 (M的H次方)/(M-1) 个节点。 // 二叉树 需要 再减去1:(M的H次方)/(M-1)-1
-
二叉树:有0个 到 2个节点的树,称为 二叉树。
-
二叉树的性质
- 性质一:在二叉树中,在第 i 层上最多有 2i-1 个节点 (i>0)。
- 性质二:深度为 k 的二叉树,最多有 2k-1 个节点 (k>0)。
- 性质三:树上的节点数为 n ,则边数一定为 n-1 。
- 性质四:任意一棵二叉树中,度为 0 的节点个数 = 度为 2 的节点个数 + 1。
- 性质五:具有 n 个节点的 完全二叉树 的深度为 ⌊ log 2 n ⌋ + 1 (向下取整)。
-
大顶堆,小顶堆
// 大顶堆 每个根节点(所有的根节点) 都大于 它的子结点 ki >= k2i k2 >= k2i +1 // 小顶堆 每个根节点(所有的根节点) 都小于 它的子结点 ki <= k2i k2 <= k2i +1
二叉树链式存储
-
结点的定义
typedef struct TreeNode{ char ch; // 数据域 struct TreeNode *lchild; //左孩子 指针 struct TreeNode *rchild; //右孩子 指针 }TreeNode;
当没有 左右孩子节点 的时候,左右孩子指针 为 NULL 值。
-
遍历方式
- 先序遍历:根左右
- 中序遍历:左根右
- 后序遍历:左右根
-
先序遍历
void preOrder(Node *root){ //递归结束条件,也就是说当一个节点 某个指针域为 NULL if(!root){ return; } printf("%c ",root->ch); //递归调用左子树 preOrder(root->lchild); //递归调用右子树 preOrder(root->rchild); }
-
中序遍历
void inOrder(Node *root){ //递归结束条件,也就是说当一个节点 某个指针域为 NULL if(!root){ return; } //递归调用左子树 preOrder(root->lchild); printf("%c ",root->ch); //递归调用右子树 preOrder(root->rchild); }
-
后序遍历
void lastOrder(Node *root){ //递归结束条件,也就是说当一个节点 某个指针域为 NULL if(!root){ return; } //递归调用左子树 preOrder(root->lchild); //递归调用右子树 preOrder(root->rchild); printf("%c ",root->ch); }
-
根据遍历的序列 确定 二叉树 的形状
// 1、先序 和 中序 可以确定一棵二叉树 答:根据先序 确定 根节点的位置,然后去 中序 找到该节点,该节点左边元素都是左子树,右边元素都是右子树;依次重复上述过程,即可根据序列确定 二叉树形状。 (从前找根节点) // 2、后序 和 中序 可以确定一棵二叉树 答:根据先序确定 根节点的位置,然很去中序中找到该节点左边元素都是左子树,右边元素都是右子树,一次重复上述过程,即可根据序列确定 二叉树形状。 (从后找根节点) // 3、先序 和 后序 不可以确定一棵二叉树
-
非递归中序遍历
void inOrder(Node * root){ //指针p 指向 根节点,并按照某种顺序游走于每一个结点 Node *p = root; //建立栈,用来实现 递归 LinkStack s = init_LinkStack(); //初始化栈,初始化一个头结点出来 //p 不指向 NULL 并且 栈不空的时候 while(p || ! isEmpty_LinkStack(s)){ //根节点不空,进栈,让其指向左子树 if(p){ push_LinkStack(s , p); p = p->lchild; //指向左子树 }else{ //定义一个指针 指向 栈顶元素 (就是 子树的根节点) Node *top = top_LinkStack(s); //元素出栈 pop_LinkStack(s); printf("%c ",top->data); p = top->rchild; } } //销毁栈 destory_LinkStack(s); }
-
如果有 n 个节点,则有 n+1 个空指针域。
-
线索:指向其 前驱 和 后继 的指针。
-
孩子兄弟表示法:左边指针是孩子,右边指针是兄弟。
二叉排序树
- 概念:是一颗空树,或者是满足以下性质的二叉树
- 左子树非空,左边节点 小于 根节点。
- 右子树非空,右边节点 大于 根节点。
- 中序遍历,可以实现二叉排序树 从小到大排序。
// 定义节点 结构体
typedef struct TreeNode{
int data; //数据域
struct TreeNode *lchild; //左指针(指向左孩子)
struct TreeNode *rchild; //右指针(指向右孩子)
}TreeNode;
TreeNode* searchTreeNode( TreeNode *root, int key ){
//递归结束条件, root 为 NULL 相当于 没有找到节点
if( (!root) || (key == root->data) ){
return root;
}
else if( key < root->data ) // 递归左子树查找
{
retuen searchTreeNode(root->lchild, key);
}
else //递归右子树查找
{
retuen searchTreeNode(root->rchile, key);
}
}
哈夫曼树(最小权值树)
-
路径:两个节点之间的 线 就是 路径。
-
节点的路径:从 根节点 到 该节点,有几根线,路径长度就是几。
-
树的路径长度:所有结点的路径长度之和,称之为树的路径长度。
-
权值:给这个节点 赋一个数值,这个数值就是 权值。
-
节点的带权路径长度:路径长度 * 该节点的权值。
-
树的带权路径长度(WPL):所有 叶子节点 的带权路径长度 之和。
-
重点:哈夫曼树构造
//哈夫曼二叉树构造 1、每次从 序列中找出 权值最小的两个节点(一般:从左至右,从小到大) 2、生成一个新的节点,将这个节点放入 序列中,删除刚才的两个最小权值节点,再重复此过程。
邻接矩阵
- 无向图 对应的临界矩阵 是 对称矩阵。
- 矩阵的大小 只与 顶点个数有关,是一个 N*N阶矩阵。
- 顶点的度:
- 入度:邻接矩阵 对应于 列上值的总和。
- 出度:邻接矩阵 对应于 行上值的总和。
邻接表
-
防止浪费空间(稀疏矩阵),但是效率 相对于 邻接矩阵 较低。
-
邻接表:顶点用 一维数组存储,后面跟着 单链表 存储邻接点。
-
邻接表 中 邻接点的个数 和 图中这个顶点 的出度有关。
带权图
- 边 给一个值,称之为 边权,对应图 称之为 带权图,也称之为 网。
深度优先遍历 DFS
- 不撞南墙不回头(遍历不唯一)。
广度优先遍历算法
- 相当于我们的二叉树层次遍历(用到队列,先进先出)。
- 记得保持顺序。
G = (V,E) :G指的图 V指的顶点 E指的边。
- 强连通图:任意顶点都能到达其他顶点,称之为:强连通图。
最小生成树
-
生成树:顶点由边连接在一起,不存回路的图。
-
生成树顶点个数 和 图顶点个数相同。
-
生成树(极小连通图) 去掉一条边,则变成非连通图。
-
连通图有 n 个顶点,则其 生成树 有 n-1条边。
-
生成树 再加一条边,必然形成回路。
含有 n 个顶点 n-1 条边的图,不一定是 生成树。
-
-
无向图的生成树
- 深度优先生成树
- 广度优先生成树
-
最小生成树:边权值之和最小的树。
普利姆算法(Prim)
- 普利姆算法 和 点 有关。
- 每次去找和点连接的最小权值的边(包括之前的点)。
克鲁斯卡尔算法(Kruskal)
-
克鲁斯卡尔 和 边 有关。
-
每次去找 边权值 最小的边,两个顶相连,依次构成最小生成树。
最短路径
迪杰斯特拉(Dijkstra)
- 两点间最短路径。
弗洛伊德(Floyd)
- 所有顶点间的最短路径。
- 两个矩阵,一个存储 权值,一个存储 路径。
- 把所有的顶点依次加入矩阵中,找最小权值路径。
拓扑排序
AOV网
- 顶点 表示 活动。
- 弧 表示活动之间的有限制约关系。
- 拓扑排序:前提 有向无环图。
- 拓扑排序:找 入度为0(无前驱) 的结点 删除,循环上述过程即可。
- 逆拓扑排序:找 出度为0(无后继) 的结点 删除,循环上述过程即可。
- 判断 AOV网 中是否有环。