请善用目录
与Ctrl+F
早期的计算机一一只用于处理纯数值型问题
现代计算机一一经常处理非数值型问题
对于非数值型的问题:
1.我们关心每个个体的具体信息
2.我们还关心个体之间的关系
即,数据元素与数据结构之间没有指定的关心:
不同的数据类型可以组成相同的数据结构;相同的数据类型可以组成不同数据结构。
线性结构:
逻辑结构
物理结构–链式存储:
物理结构–索引存储:
优点:检索速度快
缺点:附加的索引表额外占用存储空间,且增减和删除数据时也要修改索引表,因此会花费较多时间
物理结构–散列存储:
优点:检索、增加和删除节点的操作很快
缺点:若散列函数不好,则可能出现元素存储单元冲突,而解决冲突会增加时间和空间的开销
树形结构:
图形结构:
集合结构:
数据类型是一个值的集合和定义在此集合上的一组操作的总称。
1)原子类型。其值不可再分的数据类型
2)结构类型。其值可以再分解为若干成分(分量)的数据类型
**抽象数据类型(Abstract Data Type, ADT)*是抽象数据组织及与之相关的操作。
03 以下属于逻辑结构的是:
A.顺序表
B.哈希表
C.有序表
D.单链表
04 以下与数据的存储结构无关的术语是:
A.循环队列
B.链表
C.哈希表
D.栈
06 在存储数据时,通常不仅要存储各数据元素的值,而且要存储:
A.数据的操作方法
B.数据元素的类型
C.数据元素之间的关系
D.数据的存取方式
程序 = 数据结构 + 算法
正确性。算法应能够正确地解决求解问题。
可读性。算法应具有良好的可读性,以帮助人们理解。
健壮性。输入非法数据时,算法能适当地做出反应或进行处理,而不会产生莫名其妙的输出结果。
高效率与低存储量需求
(时间复杂度低)+(空间复杂度低)
结论:
运算规则:
趋向于无穷的速度::
O ( 1 ) < O ( l o g 2 n ) < O ( n ) < O ( n ∗ l o g 2 n ) < O ( n 2 ) < O ( n 3 ) < O ( 2 n ) < O ( n ! ) < O ( n n ) Ο(1)<Ο(log_2n)<Ο(n)<Ο(n*log_2n)<Ο(n^2)<Ο(n^3)<Ο(2^n)<Ο(n!)<Ο(n^n) O(1)<O(log2n)<O(n)<O(n∗log2n)<O(n2)<O(n3)<O(2n)<O(n!)<O(nn)
类型–顺序执行型:
类型–循环嵌套型:
类型–指数循环型:
类型–条件搜索型:
注:很多算法执行时间与输入的数据有关
最坏时间复杂度:最坏情况下算法的时间复杂度
平均时间复杂度:所有输入示例等概率出现的情况下,算法的期望运行时间
最好时间复杂度:最好情况下算法的时间复杂度
08 设n是描述问题规模的非负整数,下面程序片段的时间复杂度是:
x = 2; while(x<n/2) x = 2*x;
A. O ( l o g 2 n ) Ο(log_2n) O(log2n)
B. O ( n ) Ο(n) O(n)
C. O ( n l o g 2 n ) Ο(nlog_2n) O(nlog2n)
D. O ( n 2 ) Ο(n^2) O(n2)
[解]
执行频率最高的语句为
x = 2*x
每执行一次,x乘2。假设该语句最终执行t次,又因x初始值为2,所以有:2 t + 1 < n / 2 2^{t+1}
2t+1<n/2 =>( t + 1 ) l o g 2 2 < l o g 2 ( n / 2 ) (t+1)log_22
(t+1)log22<log2(n/2) =>( t + 1 ) < l o g 2 n − 1 (t+1)
(t+1)<log2n−1 =>t < l o g 2 n − 2 t
t<log2n−2 因此有 T ( n ) = O ( l o g 2 n ) T(n)=Ο(log_2n) T(n)=O(log2n)10 已知两个长度分别为 m 和 n 的升序两边,若将他们合并为长度为 m + n 的一个降序链表,则最坏情况下的时间复杂度是:
A. O ( n ) Ο(n) O(n)
B. O ( m n ) Ο(mn) O(mn)
C. O ( m i n ( m , n ) ) Ο(min(m,n)) O(min(m,n))
D. O ( m a x ( m , n ) ) Ο(max(m,n)) O(max(m,n))
[解]
一开始我的想法是,先将这两个升序链表合并成一个升序链表花费 O ( m i n ( m , n ) ) Ο(min(m,n)) O(min(m,n)) ,然后再整体使用头插法实现逆序,花费 O ( m + n ) Ο(m+n) O(m+n) 两者相加: O ( m + n + m i n ( m , n ) ) Ο(m+n+min(m,n)) O(m+n+min(m,n))
- 当 m>>n时: O ( m + n + m i n ( m , n ) ) Ο(m+n+min(m,n)) O(m+n+min(m,n))~ O ( 2 m ) Ο(2m) O(2m) ~ O ( m ) Ο(m) O(m) > > O ( n ) >>Ο(n) >>O(n)
A错;B: O ( n ) Ο(n) O(n) 故C错;而D: O ( m ) Ο(m) O(m) 满足条件
- 当 m~n时 : O ( m + n + m i n ( m , n ) ) Ο(m+n+min(m,n)) O(m+n+min(m,n)) ~ O ( n + m ) Ο(n+m) O(n+m)~ O ( 2 n ) Ο(2n) O(2n) ~ O ( n ) Ο(n) O(n)
B~ O ( n 2 ) Ο(n^2) O(n2) 因此B错;而D: O ( n ) Ο(n) O(n) ~ O ( m ) Ο(m) O(m) 满足条件
- 当 m<
O ( m + n + m i n ( m , n ) ) Ο(m+n+min(m,n)) O(m+n+min(m,n))~ O ( 2 n ) Ο(2n) O(2n) ~ O ( n ) Ο(n) O(n) 而 D: O ( n ) Ο(n) O(n) 满足条件
- 综上,D满足所有情况
12 下列函数的时间复杂度是:
int func(int n){ int i = 0,sum = 0; while(sum<n) sum += ++i; return i; }
A. O ( l o g n ) Ο(logn) O(logn)
B. O ( n 1 / 2 ) Ο(n^{1/2}) O(n1/2)
C. O ( n ) Ο(n) O(n)
D. O ( n l o g n ) Ο(nlogn) O(nlogn)
[解]
基本运算 sum += ++i,等价于 ++i;sum = sum+i,每执行一次 i 自增1。
不难发现这是一个公差为1的等差数列求和,起始项为1,尾项为n,共有n项。可知循环次数满足: ( 1 + t ) ∗ t / 2 < n (1+t)*t/2
(1+t)∗t/2<n => 时间复杂度为 O ( n 1 / 2 ) Ο(n^{1/2}) O(n1/2)扩展:
求解 递归与非递归算法下的斐波那契数列算法时间复杂度
F ( n ) = { 1 , if n = 0 , 1 ; F ( n − 1 ) + F ( n − 2 ) , if n > 1. \begin{equation} F(n) = \begin{cases} 1, & \text{if } n = 0,1; \\ F(n-1)+F(n-2), & \text{if } n >1. \end{cases} \end{equation} F(n)={1,F(n−1)+F(n−2),if n=0,1;if n>1.
- 递归求解:
int dfs(n){ if(n<=1) return 1; return dfs(n-1)+dfs(n-2); }
执行图:
即, 2 0 + 2 1 + 2 2 + . . + 2 n = 1 − 2 n 1 − 2 = 2 n − 1 2^0+2^1+2^2+..+2^n=\frac{1-2^n}{1-2}=2^n-1 20+21+22+..+2n=1−21−2n=2n−1 故时间复杂度为: O ( 2 n ) Ο(2^n) O(2n)
- 非递归求解
int dfs(int n){ if(n<=1) return 1; int[] tmp = new int[n+1];//初始化数组 tmp[1] = 1; for(int i = 2;i<=n;i++){ //其过程只依靠与前两个元素 temp[i] = temp[i-1] + temp[i-2]; } return tmp[n]; }
不难看出,时间复杂度为 O ( n ) Ο(n) O(n)
注:数据结构三要素一一逻辑结构、数据的运算、存储结构(物理结构);储存的方式不同,运算实现的方式不同
顺序表一一用顺序存储(一般使用数组)的方式实现线性表顺序存储。把逻辑上相邻的元素存储在物理
位置上也相邻的存储单元中,元素之间的关系由存储单元的邻接关系来体现。而在数组分配上又可再进一步的分为动态数组分配与静态数组分配。
#include
#include
#define MaxiSize 10 //默认顺序表的最大长度
//使用静态数组分配顺序表
/* Static Arrays*/
typedef struct SAList
{
int data[MaxiSize]; //使用静态数组存放数据
int length; //顺序表当前长度
} SASqList; //顺序表的定义(静态分配方式)
//使用动态分配顺序表
/* Malloc Arrays*/
typedef struct MAList
{
int *data; //指示动态分配数组的指针
int MaxSize; //顺序表的最大容量
int length; //顺序表的当前长度
}MASqList;
//静态数组顺序表初始化函数
void SAInitList(SASqList *L){
printf("<-------静态顺序表初始化函数------->\n");
for (int i = 0; i < MaxiSize; i++)
{
//全部数组赋初值为0
L->data[i] = 0;
}
//顺序表初始长度为0
L->length = 0;
printf("<-------静态顺序表初始化结束------->\n");
}
void MAInitList(MASqList *L){
printf("<-------动态顺序表初始化函数------->\n");
//用malloc申请一片连续的数组空间
L->data = (int *)malloc(MaxiSize * sizeof(int));
//顺序表初始长度为0
L->length = 0;
L->MaxSize = MaxiSize;
printf("<-------静态顺序表初始化结束------->\n");
}
void MAIncreaseSize(MASqList *L,int len){
int *p = L->data; //用一个指针暂存当前顺序表的数组指针
//重新给L顺序表分配空间(变大了)
L->data = (int *)malloc((L->MaxSize + len) * sizeof(int));
for (int i = 0; i < L->MaxSize; i++)
{
L->data[i] = p[i]; //将原来L顺序表中的元素赋值到新的区域
}
L->MaxSize = L->MaxSize + len; //顺序表最大长度增加 len
free(p); //释放原来的内存空间
}
int main(){
SASqList SAL; //声明一个静态顺序表
SAInitList(&SAL); //初始化静态顺序表
MASqList MAL; //声明一个动态顺序表
MAInitList(&MAL);
return 0;
}
功能代码是在静态分配方式下实现的,动态分配也雷同
/** 静态顺序表的插入 在L的位序 i 处插入元素 e
* @param L 需要操作的链表
* @param i 需要插入的位置(表L中第i个位置对应于下标i-1)
* @param e 需要插入的元素
* 这里需要注意的是,最后e应该放到 i -1 = 2 的数组下标位置
* 如 i = 2 j最后一进入 for 循环值为 2 则data[2] = data[1]
* 0 1 2 3 4 5 index
* 3 5 7 9 0 values
* 3 5 5 7 9 0 将 下标>=1的元素全部后移一位
* 3 e 5 7 9 0 将元素放置i-1=1处
*/
bool SAListInsert(SASqList *L,int i,int e){
if(i<1||i>(L->length)+1) //判断 i 的合法性
return false;
if(L->length>=MaxiSize) //如果空间已满则也不能插入
return false;
//将第 i 个元素及之后的元素后移
for (int j = L->length; j >= i; j--)
{
L->data[j] = L->data[j - 1];
}
L->data[i - 1] = e; //在位序 i 处放入e
L->length++; //长度+1
return true;
}
/** 静态顺序表的删除 将L的位序为 i 的元素删除 并将其赋至e
*@param L 需要操作的表
@param i 需要删除的位序
@param e 对应为删除的元素带回来
* 如 i = 2
* 有 e = data[1] =5
* j第一轮进入 for 循环值为 i=2 则data[1] = data[2]
* j最后一进入 for 循环值为 length-1=5-1=4 则data[3] = data[4]
* 0 1 2 3 4 5 index
* 3 5 7 9 0 values
* 3 7 9 0 0 将 下标>=2的元素全部前移一位
* */
bool SAListDelete(SASqList *L,int i , int *e){
if(i<1||i>L->length) //判断 i 的合法性
return false;
*e = L->data[i-1]; //将需要删除的元素的值赋到e
for (int j = i; j < L->length;j++){
L->data[j - 1] = L->data[j]; //将第 i 各位之后的元素前移
}
L->length--; //线性表长度-1
return true;
}
/** 获取链表L中位序为 i 的元素
* @param L 需要操作的链表
* @param i 需要取值的位序
* */
int SAGetElem(SASqList *L,int i ){
if(i<1||i>L->length) //判断位序的合法性
return -1;
return L->data[i - 1]; //与数组的使用无异
}
/** 按值查找 在顺序表中获取值 e 第一次出现的位序
* @param L 需要操作的链表
* @param e 需要查询的数值
* */
int SALocateElem(SASqList *L,int e){
for (int i = 0; i < L->data;I++){
if(L->data[i]==e) //如果匹配到对应的值
return i + 1; //范围对应的位序
}
return -1; //如果没有查询到,则返回-1
}
知识总览
单链表与顺序表的对比
链表结构体声明
typedef struct Node
{
int data; //数据域 每个节点存放一个数据
struct Node *pNext; //指针域 指针指向下一个节点
} LNode, *LinkList;
//NODE * 声明指向单链表的第一个节点的指针
//PNODE 声明指向单链表的第一个节点的指针--可读性更强
//不带头结点的空链表初始化
LinkList NHInitList(){
return NULL; //空表,没有数据
}
//带头结点的空链表初始化
LinkList HInitList(){
LinkList L = NULL;
L = (LinkList)malloc(sizeof(LNode));
if(L=NULL){
printf("创建失败!\n");
exit(-1);
}
L->pNext = NULL;
return L;
}
代码实现
//带头结点的头插法创建单链表
LinkList ListCreatHeadInsert()
{
int len; //所需创建的链表长度
LNode *s; //暂存新建节点指针
LinkList pHead; //返回的创建的链表头节点指针
//创建头节点
pHead = (LNode *)malloc(sizeof(LNode));
if(pHead==NULL)
{
printf("分配内存失败!\n");
exit(-1);
}
printf("<=========头插法=========>\n");
printf("请输入需要生产的节点数:\n");
scanf("%d", &len);
for (int val,i = 0; i < len;i++)
{
printf("请输入第%d个节点的值:", i + 1);
scanf("%d", &val);
s = (LinkList)malloc(sizeof(LNode)); //分配空间给新节点
if(s==NULL)
{
printf("分配内存失败!\n");
exit(-1);
}
s->data = val; //赋值给新创建的节点
s->pNext = pHead->pNext; //新节点接管头节点的后继节点
pHead->pNext = s; //新节点成为头节点的后继节点
}
return pHead; //返回头节点
}
重要应用:链表逆序
//利用头插法逆序带头结点的链表
LinkList Reverse(LinkList pHead){
if(HEmpty(pHead))
return pHead;
LinkList p = HInitList(); //初始化空的带头结点链表
while(!HEmpty(pHead)){
pHead = pHead->pNext; //链表指向下一个节点,注意,一开始进来需要跳过头节点,
//因为头节点不带数据
LNode *s = (LNode *)malloc(sizeof(LNode));//为新节点分配空间
s->data = pHead->data;//赋值
s->pNext = p->pNext;
p->pNext = s;
}
return p; //返回逆序后的头节点
}
代码实现
// 带头节点的尾插法创建单链表
LinkList ListCreatTailInsert(){
int len; //所需创建的链表长度
LNode *s, *r; // s:暂存新建节点指针,r保存当前的尾节点的指针
LinkList pHead; //返回的创建的链表头节点指针
//创建头节点
pHead = (LNode *)malloc(sizeof(LNode));
r = pHead; //尾指针指向pHead节点,因为是新建立的,故为最后一个节点
if(pHead==NULL)
{
printf("分配内存失败!\n");
exit(-1);
}
printf("<=========尾插法=========>\n");
printf("请输入需要生产的节点数:\n");
scanf("%d", &len);
for (int val,i = 0; i < len;i++)
{
printf("请输入第%d个节点的值:", i + 1);
scanf("%d", &val);
s = (LinkList)malloc(sizeof(LNode)); //分配空间给新节点
if(s==NULL)
{
printf("分配内存失败!\n");
exit(-1);
}
s->data = val; //赋值给新创建的节点
s->pNext = NULL; //新节点的后继节点置空
r->pNext = s; //尾节点的后继节点指向新节点
r = s; //尾节点指向新节点
}
/*
也可以将上面的语句 s->pNext = NULL 从循环中删除
改成在循坏外最后执行:r->pNext = NULL;
*/
return pHead; //返回头节点
}
知识总览
代码实现
/** 带头结点的单链表按位序插入
* @param L 操作的链
* @param i 需要插入的位序
* @param e 需要插入的元素
* 头节点作为第0号节点,而插入第 i 位节点可看成插入第 i -1 节点之后
* 故我们需要定位到 第 i - 1 节点(因为链表插入到某个节点后,需要要知道该节点的前继节点)
* 下面定位节点的 for 循环功能每次执行完一次,指针都会指向到下一个下一个节点
* 因此如果我们想要当最后一次执行完for循环之后能正确的指向我们所需的节点位置( 第i - 1节点)
* 我们就得需要却确保最后一次进入for循环时遍历到的是第 i -2 个节点
* 故下面for循环的终止条件为 j< i-1 <=> j<= i - 2
* */
bool HListInsert(LinkList *L, int i, int e){
if(i<i) //判断位序的合法性
return false;
LNode *p; //指针p记录当前扫描到的节点
int j = 0; //当前p指向的是第几个节点
p = L; //L指向头节点,头节点是第0个节点(不存数据)
//循环找到第 i-1 个节点
while(p!=NULL&&j<i-1){
p = p->pNext;
j++;
}
if(p==NULL)//i值不合法
return false;
//分配新节点空间
LNode *s = (LNode *)malloc(sizeof(LNode));
s->data = e; //赋值给新节点
s->pNext = p->pNext; //将p节点看成头节点
p->pNext = s; //使用 头插法
return true;
}
几种情况:
代码实现
/** 不带头结点的按序插入
* @param L 需要操作的链表 //L指向的是第一个节点元素指针的指针
* @param i 需要插入的位序
* @param e 需要插入的元素
* 注意,由于不带头结点,插入第一位时需要修改头指针指向,所以传参数时
* 不能值传递,只能指针传递,即传递指针的指针
* */
bool NHListInsert(LinkList *L, int i, int e) {
if(i<1)
return false; //判断位序的合法性
//因为不带头结点,故需指要单独判断和操作。具体的,让新节点插入表头
//并让表头指针指向该节点
LNode *s = (LNode *)malloc(sizeof(LNode)); //给新结点分配空间
if(i==1){
s->data = e; //给新结点赋值
s->pNext = (*L); //将头节点插入表头
(*L) = s; //将头指针的指向新节点
return true;
}
/* 以下代码段同头插法的寻找第 i-1 个节点 */
LNode *p = *(L); //指针p指向当前扫描到的节点
int j = 1; //当前p指向的第几个几点,注意,由于是不带头节点的,故从1开始
while(p!=NULL&&j<i-1){
p = p->pNext;
j++;
}
if(p==NULL) //i值不合法,即i大小超过当前链表长度
return false;
s->data = e;
s->pNext = p->pNext;
p->pNext = s;
return true;
}
需要特别注意的是:i=1插入表首的情况
其他情况与带头结点一致:
代码实现
/** 插入到指定节点 p 后面
* @param P 指定的节点
* @param e 需要插入的元素
* */
bool HInsertNextNode(LNode *p, int e){
if(p==NULL)
return false;
LNode *s = (LNode *)malloc(sizeof(LNode));
if(s==NULL)
return false;
s->data = e;
s->pNext = p->pNext; //后插
p->pNext = s;
return true;
}
解法一:
/** 将元素 e 前插到指定节点 p 之前
* @param L 需要菜操作的链表头指针
* @param P 指定的节点
* @param e 需要插入的元素
**/
bool HInsertPriorNode(LinkList L, LNode *p, int e){
//如果需要插入到头节点之后,即需要充当第一个节点
if(L==p){
LNode *s = (LNode *)malloc(sizeof(LNode));
s->data = e;
s->pNext = L->pNext; //1-1使用头插法将s插到头节点L之后
L->pNext = s; //1-2使用头插法将s插到头节点L之后
return true;
}
LNode *r = L->pNext; //创建尾指针 初始化为指向头节点
/* r指针寻找目标节点:p ;L指针充当r的前继结点的指针 */
while(r!=NULL){
//如果 r 指针找到目标节点 p
if(r==p){
LNode *s = (LNode *)malloc(sizeof(LNode));
s->data = e;
s->pNext = L->pNext; // 1-1将s节点接到L节点后面
L->pNext = s; // 1-2将s节点接到L节点后面
return true;
}
//如果没有找到,继续进行指针的传递
else{
L = r->pNext;
r = r->pNext;
}
}
//没有找到p,结束
return false;
}
解法二:
/** 将元素 e 前插到指定节点 p 之前
* @param p 指定的节点
* @param e 需要插入的元素
* */
bool HInsertPriorNodeC(LNode *p, int e){
if(p==NULL)
return false;
LNode *s = (LNode *)malloc(sizeof(LNode));
if(s==NULL)
return false;
s->pNext = p->pNext; //1.1将新节点插入到指定节点p之后
p->pNext = s; //1.2将新节点插入到指定节点p之后
s->data = p->data; //2.1交换s与p两节点的数据
p->data = e; //2.2交换s与p两节点的数据
return true;
}
代码实现
/** 带头结点删除位序为 i 的节点 并将删除的元素赋给 e
* @param L 需要操作的链表
* @param i 需要删除的节点位序
* @param e 将删除的值赋给e
* */
bool HListDelete(LinkList L, int i, int *e){
if(i<1) //判断i的范围合法性
return false;
LNode *p; //指针p指向的是当前扫描的节点
p = L; //开始指向头节点
int j = 0; //L指向头节点,头结点是第0个节点,不存数据
//循环找到第 i-1 个节点
while(p!=NULL&&j<i-1){
p = p->pNext;
j++;
}
if(p==NULL) //如果 i 值不合法
return false;
if(p->pNext==NULL) //第 i-1 个节点之后已无其他节点
return false;
LNode *q = p->pNext; //令q指向被删除的节点
*e = q->data;
p->pNext = q->pNext; //将*q节点从从链中“断开”
free(q); //释放节点的存储空间
return true; //删除成功
}
不带头结点
/** 不带头结点删除位序为 i 的节点 并将删除的元素赋给 e
* @param L 需要操作的链表
* @param i 需要删除的节点位序
* @param e 将删除的值赋给e
* */
bool NHListDelete(LinkList *L, int i, int *e){
if(i<1||L==NULL) //判断 i 与 指针 的合法性
return false;
if(i==1){
LNode *q = (*L);
*e = q->data;
*L = q->pNext;
free(q);
return true;
}
//<<=====余下操作同带头结点的====>>//
LNode *p; //指针p指向当前扫描的节点
p = *L; //开始指向首节点
int j = 1; //L指向首节点,首节点是第1个节点
//循环找到第 i-1 个节点
while(p!=NULL&&j<i-1){
p = p->pNext;
j++;
}
if(p==NULL) //如果 i 值不合法
return false;
if(p->pNext==NULL) //第 i-1 个节点之后已无其他节点
return false;
LNode *q = p->pNext; //令q指向被删除的节点
*e = q->data;
p->pNext = q->pNext; //将*q节点从从链中“断开”
free(q); //释放节点的存储空间
return true; //删除成功
}
方法一:传入头指针,查找所需删除节点p的前继结点
代码实现:
/** 删除指定节点 方法一:查找前继结点
* @param L 需要操作的链表
* @param p 需要删除的节点
* */
bool HDeleteNode(LinkList L, LNode *p){
LNode *r = L->pNext; //设置扫描尾指针,指向当前扫描到的值,初始化为第一个节点
//查找需要删除的节点的前继结点 用L指向;此时r匹配到需要删除的节点
while(r!=NULL&&r!=p){
L = r; //1-1进行指针传递
r = r->pNext; //1-2进行指针传递
}
if(r==NULL)
return false;
L->pNext = r->pNext; //将指针r从链表中“断开”
free(r);
return true;
}
方法二:与后继节点交换数据,并将后继节点删除
代码实现:
/** 删除指定节点
* 交换p节点与其后继节点之间的数据,然后将后继节点删除
* @param p 需要删除的节点
* */
bool HDeleteNode1(LNode *p){
if(p==NULL)
return false;
LNode *q = p->pNext; //令q指向*p的后继节点
p->data = p->pNext->data; //和后继节点交换数据 注意,如果p节点为最后一个节点的话,该语句会出错
p->pNext = q->pNext; // 将*q节点从链中"断开"
free(q); //释放后继节点的存储空间
return true;
}
注意,该方法不能删除最一个节点
注意,以下代码是建立在包含头节点的链表中实现的
代码实现;
/** 删除指定节点
* 交换p节点与其后继节点之间的数据,然后将后继节点删除
* @param p 需要删除的节点
* */
bool HDeleteNode1(LNode *p){
if(p==NULL)
return false;
LNode *q = p->pNext; //令q指向*p的后继节点
p->data = p->pNext->data; //和后继节点交换数据 注意,如果p节点为最后一个节点的话,该语句会出错
p->pNext = q->pNext; // 将*q节点从链中"断开"
free(q); //释放后继节点的存储空间
return true;
}
/** 按位查找,返回第 i 个节点元素(包含头节点)
* @param L 需要操作的链表
* @param i 需要返回的节点位序
* */
LNode *HGetElem(LinkList L, int i){
if(i<1) //判断位序的合法性
return NULL;
LNode *p = L; //p指针指向当前扫描到的节点元素
int j = 0; //p初始化为指向头节点,头节点属与第0号节点
//循环找到第 i 个节点
while(p!=NULL&&j<i){
p = p->pNext;
j++;
}
return p;
}
/** 按位查找,返回第 i 个节点元素(包含头节点)
* @param L 需要操作的链表
* @param i 需要返回的节点位序
* */
LNode *HGetElem(LinkList L, int i){
if(i<1) //判断位序的合法性
return NULL;
LNode *p = L; //p指针指向当前扫描到的节点元素
int j = 0; //p初始化为指向头节点,头节点属与第0号节点
//循环找到第 i 个节点
while(p!=NULL&&j<i){
p = p->pNext;
j++;
}
return p;
}
/** 按值查找,返回值=e的节点(包含头节点)
* @param L 需要操作的链表
* @param e 需要查找的元素
* */
LNode *HLocateElem(LinkList L, int e){
LNode *p = L->pNext;
//从第1个节点开始查找数据域为e的节点
while(p!=NULL&&p->data!=e){
p = p->pNext;
}
return p; // 若找到则返回对应的节点,否则返回NULL
}
int Length(LinkList L){
int len = 0;
LNode *p = L;
while(p->pNext!=NULL){
len++;
p = p->pNext;
}
return len;
}
bool InitDLinkList(DLinkList *L){
(*L) = (DNode *)malloc(sizeof(DNode)); //分配一个头结点
if((*L) == NULL)
return false;
(*L)->prior = NULL; //头节点的prior永远指向NULL
(*L)->pNext = NULL; //头节点之后展示还没有节点
return true;
}
/** 在节点 p后面插入s新节点
* @param p 需要在该节点之后插入新节点
* @param s 需要新插入的节点
*/
bool InsertNextDNode(DNode *p, DNode *s){
if(p==NULL||s==NULL) //非法参数
return false;
s->pNext = p->pNext; //①将新节点s的后指针指向p的后继节点
s->prior = p; //②将新节点s的前指针指向p节点
if(p->pNext!=NULL)
p->pNext->prior = s; //③将p的后继节点的前指针指向新节点s
p->pNext = s; //④将p后指针指向新节点s
return true;
}
//批量添加节点
bool Add(DLinkList *L){
int len, i = 0;
DNode* node = (*L);
printf("请输入需要创建的节点个数:\n");
scanf("%d", &len);
while(--len>=0&&++i>0){
int val;
printf("请输入当前第 %d 个元素的数值:", i);
scanf(" %d", &val);
DNode* newNode = (DNode *)malloc(sizeof(DNode));
if(newNode == NULL) // 内存空间分配失败
return false;
newNode->data = val;
InsertNextDNode(node, newNode);
node = newNode;
}
}
/** 删除p节点的后继节点
* p需要操作的节点
*/
bool DeleteNextDNode(DNode *p){
if(p==NULL)
return false;
DNode *q = p->pNext; //找到p的后继节点q
if(q==NULL)
return false; //p没有后继节点
p->pNext = q->pNext;
free(q); //释放p的后继节点的空间
return true;
}
双链表不可随机存取,按位查找、按值查找操作都只能用遍历的方式实现。时间复杂度O(n)
/** 从节点p开始遍历双链表
* @param p 起始节点
* @param e 选项,1:后向遍历、2:前向遍历、3:前向遍历(跳过头节点)
*/
void traverse_DList(DNode *p,int e){
if(p==NULL)
return;
switch (e)
{
case 1: //后向遍历
while(p!=NULL){
printf("%d\n", p->data);
p = p->pNext;
}
break;
case 2://前向遍历
while(p!=NULL){
printf("%d\n", p->data);
p = p->prior;
}
break;
case 3: //前向遍历(跳过头节点)
while (p->prior != NULL)
{
printf("%d\n", p->data);
p = p->prior;
}
break;
default:
break;
}
}
typedef struct CNode{
int data; //定义单链表节点类型
struct CNode *pNext; //每个节点存放一个数据元素
} CNode, *CLinkList; //指针指向下一个节点
//初始化一个循环单链表
CLinkList InitList(){
CLinkList L = (CLinkList)malloc(sizeof(CLinkList)); //分配一个头结点
if(L==NULL)
exit - 1; //内存分配不足
L->pNext = L; //头结点pnext指向头结点
return L;
}
//判断循环单链表是否为空
bool Empty(CLinkList L){
if(L->pNext==L)
return true;
else
return false;
}
//判断节点p是否为循环单链表的表尾节点
bool isTail(CLinkList L ,CNode *p){
if(p->pNext == L)
return true;
else
return false;
}
typedef struct CDNode{
int data;
struct CDNode *prior, *pNext;
} CDNode, *CDLinkList;
//初始化孔的循环双链表
CDLinkList InitCDLinkList(){
CDNode *L = (CDNode *)malloc(sizeof(CDNode)); // 分配一个头结点
if(L==NULL)
exit - 1; //内存不足,分配失败
L->prior = L; //头结点的prior指向头结点
L->pNext = L; //头结点的pNext指向头结点
return L;
}
//判断循环双链表是否为空
bool Empty(CDLinkList L){
if(L->pNext == L)
return true;
else
return false;
}
//判断p节点是否为循环双链表的尾结点
bool isTail(CDLinkList L, CDNode *p){
if(p->pNext == L)
return true;
else
return false;
}
//将节点 *s插入到节点 *p之后
bool InsertNextCDNode(CDNode *p, CDNode *s){
//过程与双链表的插入类似,只是少了判断p节点后面是否为空这一步
s->pNext = p->pNext;
s->prior = p;
p->pNext->prior = s;
p->pNext = s;
}
//删除节点p的后继节点
bool delNextCDNode(CDNode *p){
if(p == NULL)
return false;
CDNode *q = p->pNext;
p->pNext = q->pNext;
q->pNext->prior = p;
free(q);
return true;
}
方法一:
struct Node{
int data;
int next;
};
typedef struct Node SLinkList[MaxSize];
int main(void){
SLinkList sl;
sl[0].data = 0;
sl[0].next = 1;
printf("%d\t%d", sl[0].data, sl[0].next);
return 0;
}
方法二:
struct Node{
int data;
int next;
};
int main(void){
struct Node sl[MaxSize];
sl[0].data = 0;
sl[0].next = 1;
printf("%d\t%d", sl[0].data, sl[0].next);
return 0;
}
方法三:
typedef struct {
int data; //存放数据元素
int next; //下一个元素的数组下标
} SLinkList[MaxSize];
int main(void){
SLinkList sl;
sl[0].data = 0;
sl[0].next = 1;
printf("%d\t%d", sl[0].data, sl[0].next);
return 0;
}
//初始化一个空的静态链表,
//初始空状态,头结点0号节点next = -1表示该节点为尾结点,其余节点 next =-2表示空闲
bool InitSList(SLinkList *sl){
if(MaxSize<1)//最大长度不足
return false;
int cnt = 1;
(*sl)[0].next = -1;
while (cnt < MaxSize)
{
(*sl)[cnt].next = -2;
cnt++;
}
return true;
}
//判空
bool Empty(SLinkList sl){
return MaxSize == 0 || sl[0].next == -1;
}
//遍历静态链表
void traverse_SList(SLinkList sl){
int index = 0;
while(index!=-1&&index != -2){ //-1:表示表尾;-2:表示空闲结点
printf("index is : %d \t value is : %d \n", index, sl[index].data);
index = sl[index].next;
}
}
/** 插入val元素至位序为i的节点中
* @param sl 需要操作的链表
* @param i 需要插入的位序
* @param vla 需要插入的节点
*/
bool Insert(SLinkList sl, int i,int val){
if(i<0||i>MaxSize) //参数非法
return false;
int cnt = 0; // 0号头结点不存放数据
while (sl[++cnt].next != -2); //寻找一个空闲的节点:next = -2
sl[cnt].data = val; //给新节点存入数据
int preIndex = FindPreIndex(sl, i); //查找位序为 i-1 的结点的下标
sl[cnt].next = sl[preIndex].next; //修改新节点的next为 i-1 节点的next
sl[preIndex].next = cnt; //修改i-1 号节点的next指向新节点
}
/** 查找后继节点为"位序i的节点"的next
* @param sl 需要操作的链表
* @param i 需要差查找的位序
* @return -1 :查找失败 其他数值:index对应的前继节点的下标
*/
int FindPreIndex(SLinkList sl, int i){
if(i < 1||i>MaxSize) //数值非法
return -1;
int pindex = 0;
int index = sl[pindex].next, cnt = 1; //双指针查找前置节点
while (index != -1 && cnt++ < i)
{
pindex = index;
index = sl[pindex].next;
}
return pindex;
}
//删除位序为i的结点
bool Del(SLinkList sl, int i){
if(i<1||i>MaxSize)
return false;
int preIndex = FindPreIndex(sl, i); //查找位序为 i-1 的结点的下标
int iNext = sl[sl[preIndex].next].next;
sl[sl[preIndex].next].next = -2; //置被删除的结点next = -2,表示空闲
sl[preIndex].next = iNext; //将i-1的结点的next的修改为被删除的结点的next
return true;
}
逻辑结构:都属于线性表,属于线性结构
存储结构:
基本操作:
特点:后进先出(LIFO)
栈常考题型:合法的出栈顺序
#define MaxSize 10 //定义栈中元素的最大个数
typedef struct{
int data[MaxSize]; //静态数组存放栈中元素
int top; //栈顶指针
} SqStack;
初始化
//初始化栈
void InitStack(SqStack *s){
(*s).top = -1; //初始化栈顶指针
}
判空
//判空
bool StackEmpty(SqStack s){
return s.top == -1;
}
入栈
//入栈
bool Push(SqStack *s, int x){
if((*s).top==MaxSize-1) //栈满
return false;
(*s).top = (*s).top + 1; //指针先+1
(*s).data[(*s).top] = x; // 新元素入栈
return true;
}
出栈
//出栈
bool Pop(SqStack(*s), int *x){
if((*s).top==-1) // 栈空
return false;
(*x) = (*s).data[(*s).top];//先出栈
(*s).top = (*s).top - 1; //栈顶指针-1
return true;
}
读取栈顶元素
//读栈顶元素
bool GetTop(SqStack s, int *x){
if(s.top==-1)
return false;
(*x) = s.data[s.top];
return true;
}
//共享栈
typedef struct{
int data[MaxSize]; //静态数组中存放栈中元素
int top0; //0号栈顶指针
int top1; //1号栈顶指针
} ShStack;
//初始化栈
void InitStack(ShStack *s){
(*s).top0 = -1; //初始化栈顶指针
(*s).top1 = MaxSize;
}
//共享栈判满条件:top0+1==top1
typedef struct Node{
int data;//数据域
struct Node *pNext;//指针域
} SNode, *LinkStack;//栈类型定义
基本操作
//初始化栈
void InitStack(LinkStack *s){
(*s) = NULL;
}
//入栈
void Push(LinkStack *s, int x){
if((*s)==NULL){
printf("Is NUll Stack , will create a node\n");
(*s) = (SNode *)malloc(sizeof(SNode));
(*s)->data = x;
(*s)->pNext = NULL;
}
else{
SNode *p = (SNode *)malloc(sizeof(SNode));
if(p==NULL) //内存空间不足
exit(-1);
p->data = x;
p->pNext = (*s);
(*s) = p;
}
}
//出栈
bool Pop(LinkStack *s, int *x){
if(s==NULL){//栈空
printf("Is NUll Stack \n");
return false;
}
SNode *p = (*s);
(*x) = p->data;
(*s) = p->pNext;
free(p);
return true;
}
//读栈顶元素
bool GetTop(LinkStack s, int *x){
if(s==NULL){//栈空
printf("Is NUll Stack \n");
return false;
}
(*x) = s->data;
return true;
}
//遍历栈
void Traverse(LinkStack s){
while(s!=NULL){
printf("%d\t", s->data);
s = s->pNext;
}
}
//初始化队列
void InitQueue(SqQueue *Q){
//初始化时 队头队尾指针指向0
(*Q).front = (*Q).rear = 0;
}
//判断队列是否为空
bool QueueEmtpy(SqQueue Q){
return Q.front == Q.rear;
}
//入队
bool EnQueue(SqQueue *Q, int x){
if(((*Q).rear+1)%MaxSize==(*Q).front)
return false; //队列满
(*Q).data[(*Q).rear] = x; //将x插入队尾
(*Q).rear = ((*Q).rear + 1) % MaxSize; //队尾指针+1取模
return true;
}
//出队 (删除一个元素并用x返回)
bool DeQueue(SqQueue *Q, int *x){
if(QueueEmtpy(*Q))
return false; //队列空
(*x) = (*Q).data[(*Q).front];
(*Q).front = ((*Q).front + 1) % MaxSize;
return true;
}
//取出对头元素
bool GetHead(SqQueue Q, int *x){
if(QueueEmtpy(Q))
return false; //对空
(*x) = Q.data[Q.front];
return true;
}
//求队列长度
int LenQueue(SqQueue Q){
return (Q.rear - Q.front + MaxSize) % MaxSize;
}
方法一:
方法二:
方法三:
上述的关于队头队尾指针定义:队头指针front指向对头元素;队尾指针rear指向队尾元素的后一个位置。
但是还可定义:队头指针front指向对头元素;队尾指针rear指向队尾位置。
初始化时:front = 1;rear = MaxSize-1;
判空:(rear+1)%MaxSize == front;
判满:(rear+2)%MaxSize == front;
//初始化队列(带头结点)
void HInitQueue(LinkQueue *Q){
//初始化时 front 与 rear都指向头结点
(*Q).front = (*Q).rear =
(LinkNode *)malloc(sizeof(LinkNode));
//头结点的后继节点为空
(*Q).front->pNext = NULL;
}
//判断队列是否为空
bool HIsEmpty(LinkQueue Q){
return Q.front == Q.rear;
}
//初始化队列(不带头节点)
void NHInitQueue(LinkQueue *Q){
//初始化时 front 、rear 均指向NUll
(*Q).front = (*Q).rear = NULL;
}
//判断队列是否为空(带不头结点)
bool NHIsEmpty(LinkQueue Q){
return Q.front == NULL;
}
//入队(带头结点)
void HEnQueue(LinkQueue *Q,int x){
LinkNode *s = (LinkNode *)malloc(sizeof(LinkNode));
s->data = x;
s->pNext = NULL;
(*Q).rear->pNext = s; //新节点插入到rear之后
(*Q).rear = s; //修改表尾指针
}
//入队(不带头结点)
void NHEnQueue(LinkQueue *Q,int x){
LinkNode *s = (LinkNode *)malloc(sizeof(LinkNode));
s->data = x;
s->pNext = NULL;
//不带头节点的队列第一个元素入队需要特殊处理
if((*Q).front==NULL){ //在空队列中插入第一个元素
(*Q).front = s; //修改队头队尾指针
(*Q).rear = s;
}
else{
(*Q).rear->pNext = s;//新节点插入到 rear 节点之后
(*Q).rear = s; //修改 rear 指针
}
}
//出队(带头结点)
bool HDeQueue(LinkQueue *Q, int *x){
if(HIsEmpty(*Q))
return false;//堆空
LinkNode *p = (*Q).front->pNext;
(*x) = p->data; //用变量x返回队头元素
(*Q).front->pNext = p->pNext;//修改头结点的 pNext 指针
if((*Q).rear==p) //如果要出队的结点为最后一个
(*Q).rear = (*Q).front;//则修改队尾指针,指向头结点(因为队内没有节点了)
free(p);
return true;
}
//出队(不带头结点)
bool NHDeQueue(LinkQueue *Q, int *x){
if(NHIsEmpty(*Q))
return false; //空队列
LinkNode *p = (*Q).front;//P指向此次出队的结点
(*x) = p->data; //用变量x返回对头元素
(*Q).front = p->pNext; //修改 front 指针
if((*Q).rear==p){ //此次是最后一个结点出队
(*Q).front = NULL;//front 与 rear 指针指向 NULL
(*Q).rear = NULL;
}
free(p);
return true;
}
如何判断此种队列的序列合法性?因为只能从一端输入,故当某个点输出时,他之前的序列是已经在队内按照指定的输入顺序确定了,此时看能否通过两端的删除构造出对应的序列。
如何判断此种队列的序列合法性?因为只能从一端输出,故当某一个点输出时,他之前入队的序列是已经在队内确定的了。因此看能否通过两遍的输入来拼凑出对应的队列。
流程图
算法实现
bool BracketCheck(char str[], int length){
LinkStack s;
InitStack(&s); //初始化一个栈
for (int i = 0; i < length;i++){
if(str[i]=='('||str[i]=='['||str[i]=='{'){ //若为左括号 则入栈
Push(&s, str[i] - '0');
}
else{
if(StackEmpty(s)) //若为右括号 且左括号的栈空 则返回false
return false;
else{
int top;
Pop(&s, &top); //获取左括号的栈顶元素
if(str[i]==')'&&(top+'0')!='(')
return false;
if(str[i]=='}'&&(top+'0')!='{')
return false;
if(str[i]==']'&&(top+'0')!='[')
return false;
}
}
}
return StackEmpty(s);//检索完全部括号之后左括号栈为空(没有多余的左括号)这说明匹配成功
}
知识总览
中缀、后缀、前缀表达式
用栈实现,中缀转后缀+后缀表达式求值 两算法结合
利用栈,将函数递归调用转变为非递归调用
行优先:
列优先:
在按行存储且只存储了下半三角的情况利用对称性求上半三角元素
压缩策略
B数组多留出一个来存储常数C
三对角矩阵性质&坐标映射
二维转一维:
除去第一行与最后一个每行均有两个元素之外其余的每行都有三个元素:总共有 ( n − 2 ) ∗ 3 + 2 ∗ 2 = 3 n − 2 (n-2)*3+2*2=3n-2 (n−2)∗3+2∗2=3n−2个元素 因为B数组是从0开始存储的,故B最后一个元素下标为 3 n − 3 3n-3 3n−3
另外, 2 i + j − 2 2i+j-2 2i+j−2是由: 3 ( i − 1 ) − 1 + j − i + 2 3(i-1)-1+j-i+2 3(i−1)−1+j−i+2而来 ,同样的,因为B数组下标从0开始,故k要再多-1: k = 2 i + j − 3 k=2i+j-3 k=2i+j−3
一维转二维:
因为B的下标是从0开始的,故第 k + 1 k+1 k+1个对应的是下标为第 k k k个,假设按照从二维转换一维的对应关系,则有:k对应的是二维数组中的下标 i,j 。又因为一行有2、3个的元素,而一个k对应的i对应某一行。因此k位于这i行全部的元素个数与前总i-1行元素个之间。 3 ( i − 1 ) − 1 < k + 1 ≤ 3 i − 1 3(i-1)-1
上面求得了i的表达式,再代入二维转一维的表达式可得余下的j :
失去随机存储特性
![]() |
![]() |
枚举主串 S 中的每个字符作为「发起点」,每次从原串的「发起点」和匹配串的「首位」开始尝试匹配:
匹配成功:返回本次匹配的原串「发起点」。
匹配失败:枚举原串的下一个「发起点」,重新尝试匹配。
王道书上
Java实现
class Solution {
public int strStr(String ss, String pp) {
int n = ss.length(), m = pp.length();
char[] s = ss.toCharArray(), p = pp.toCharArray();
// 枚举原串的「发起点」
for (int i = 0; i <= n - m; i++) {
// 从原串的「发起点」和匹配串的「首位」开始,尝试匹配
int a = i, b = 0;
while (b < m && s[a] == p[b]) {
a++;
b++;
}
// 如果能够完全匹配,返回原串的「发起点」下标
if (b == m) return i;
}
return -1;
}
}
KMP算法见附件
先序遍历
void PreTraverse(PNodeBintree P)
{
if(P!=NULL)
{
visit(P);
PreTraverse(P->LeftChild);
PreTraverse(P->RightChild);
}
}
//非递归版
void PreTraverse2(PNodeBintree P){
if(!P)
return;
LinkStack s;
InitStack(&s); //初始化栈
while(P||!IsEmptyStack(s)){
if(P){ //节点非空
Visit(P); //先序访问根节点
Push(&s, P); //访问完,根节点入栈
P = P->LeftChild; //左孩子不空,一直往左走
}
else{
Pop(&s, &P); //出栈,并转向栈节点的右子树
P = P->RightChild; //p节点赋值
}
}
}
中序遍历
void InOrderTraverse(PNodeBintree P)
{
if(P!=NULL)
{
PreTraverse(P->LeftChild);
visit(P);
PreTraverse(P->RightChild);
}
}
//非递归版
void InOrderTraverse2(PNodeBintree P){
if(!P)
return;
LinkStack s;
InitStack(&s);
while(P||!IsEmptyStack(s)){
if(P){
Push(&s, P);
P = P->LeftChild;
}
else{
Pop(&s, &P);
Visit(P);
P = P->RightChild;
}
}
}
后序遍历
void PostOrderTraverse(PNodeBintree P)
{
if(P!=NULL)
{
PreTraverse(P->LeftChild);
PreTraverse(P->RightChild);
visit(P);
}
}
//非递归版
void PostOrderTraverse2(PNodeBintree P){
if(!P)
return;
LinkStack s;
NodeBIntree *r = NULL; //用来记录最后一访问的结点
InitStack(&s);
while(P||!IsEmptyStack(s)){
if(P){ //走到最左边
Push(&s, P);
P = P->LeftChild;
}
// else{ 错误
// Pop(&s, &P);
// if(!(P->RightChild))
// Visit(P);
// else{
// P = P->RightChild;
// }
// }
else{
GetTop(s, &P); //获取栈顶元素
if(P->RightChild&&P->RightChild!=r){ //有右子树,且并为被访问,故转向访问栈顶元素的右子树
P = P->RightChild;
}
else{ //弹出栈顶元素并访问
Pop(&s, &P);
Visit(P);
r = P; //记录最近访问的结点
P = NULL; //因为后续遍历根节点是最后一个访问的,当访问完根节点之后,
//则说明以该节点为根的树已经被访问完了
}
}
}
}
void PostOrderTraverse3(PNodeBintree P){
Stack2 st[11331];
int top = 0;
while(P||top!=0){
while(P){
st[++top].data = P;
st[top].tag = 0; //标记该节点左子树被访问
P = P->LeftChild; //一路向走,并不停入栈结点
}
while(top!=0&&st[top].tag==1){ //tag=1表示该节点的右子树已经被访问完了
// st[top].tag = 0; //将该栈节点标记恢复至初 0,因为遍历顺序先左子树
//不需要重新给出栈的栈节点赋tag值,因为top始终指向的是形成一条路劲的结点,
// 不会被已经出栈的栈节点tag干扰,如果新入了节点,则会在上面的语句中用0将其覆盖
Visit(st[top--].data); //此时左右子树均被访问完,该推出栈顶根节点并访问
}
if(top!=0&&st[top].tag==0){ //当前栈顶元素只被访问了左子树,此时需要访问右子树
P = st[top].data->RightChild; //获取栈顶结点的右子树
st[top].tag = 1; //标记访问了右子树
}
}
}
练习
可按照先序+中序方法来做
设计一个由先序序列与后序序列确定一棵二叉树的算法
//由先序序列和中序序列确定一颗二叉树
#include
#include
#include
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
};
struct TreeNode *PreInCreat(int *, int *, int, int, int, int);
struct TreeNode *buildTree(int *, int, int *, int);
void PreTraverse(struct TreeNode *);
void InOrderTraverse(struct TreeNode *);
int main(void){
int preorder[5] = {3, 9, 20, 15, 7};
int inorder[5]= {9, 3, 15, 20, 7};
struct TreeNode *root = buildTree(preorder, 5, inorder, 5);
printf("<-------先序遍历------->");
PreTraverse(root);
printf("<-------中序遍历------->");
InOrderTraverse(root);
return 0;
}
struct TreeNode *buildTree(int *preorder, int preorderSize, int *inorder, int inorderSize)
{
return PreInCreat(preorder, inorder, 0, preorderSize - 1, 0, inorderSize - 1);
}
/**
* @param preorder 先序遍历序列数组
* @param inorder 中序遍历序列数组
* @param preorder_left 先序遍历序列左边界
* @param preorder_right 先序遍历序列右边界
* @param inorder_left 中序遍历序列左边界
* @param inorder_right 中序遍历序列右边界
*/
struct TreeNode *PreInCreat(int *preorder, int *inorder, int preorder_left, int preorder_right, int inorder_left, int inorder_right){
if (preorder_left > preorder_right) {
return NULL;
}
//先序遍历的第一个节点就是根节点
int preorder_root = preorder_left;
//在中序遍历序列中查找根节点「preorder_root」的下标序号
int inorder_root;
for (inorder_root = inorder_left; inorder[inorder_root] != preorder[preorder_root]; inorder_root++);
//创建根节点
struct TreeNode *root = (struct TreeNode *)malloc(sizeof(struct TreeNode));
root->val = preorder[preorder_root];
// 得到左子树中的节点数目
int size_left_subtree = inorder_root - inorder_left;
// 递归地构造左子树,并连接到根节点
// 先序遍历中「从 左边界+1 开始的 size_left_subtree」个元素就对应了中序遍历中「从 左边界 开始到 根节点定位-1」的元素
root->left = PreInCreat(preorder, inorder, preorder_left + 1, preorder_left + size_left_subtree, inorder_left, inorder_root - 1);
// 递归地构造右子树,并连接到根节点
// 先序遍历中「从 左边界+1+左子树节点数目 开始到 右边界」的元素就对应了中序遍历中「从 根节点定位+1 到 右边界」的元素
root->right = PreInCreat(preorder, inorder, preorder_left + 1 + size_left_subtree, preorder_right, inorder_root + 1, inorder_right);
return root;
}
达咩
达咩
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dabYs0V1-1663136701592)(https://raw.githubusercontent.com/qyhc/image/master/Blog/202207311746480.png)]
转换关系:”左孩子右兄弟”–最后转换成的二叉树中,父子关系在对应的森林关系中可能是兄弟关系或是原本就是父子关系
本质上为:树的双亲表示法
思路:
状态/操作 | 图 |
---|---|
原始 | ![]() |
删除A-B边 | |
删除E及关联边 |
![]() |
![]() |
见ppt
顺序:1-5 | 6-11 |
---|---|
见ppt
执行过程见ppt
执行过程见ppt
过程见ppt
注意:顶点不可能出现重复操作数
性质
关键路径
![]() |
![]() |
![]() |
情况:
除留余数法
直接定址法
数学分析法
平方取中法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RNt06wTW-1663136701764)(https://raw.githubusercontent.com/qyhc/image/master/Blog/202208271607313.png)]
插入时的冲突
查找
删除
查找效率