赞
踩
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上软件测试知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
+ 优点:离散的小空间分配方便,改变容量方便
+ 缺点:不可随机存取,存储密度低
3. 基本操作 - 创建
4. 基本操作 - 销毁
顺序表:修改 Length = 0
typedef struct{
ElemType \*data;
int MaxSize;
int length;
}SeqList;
//创
L.data = (ELemType \*)malloc(sizeof(ElemType) \*InitSize)
//销
free(L.data);
//!malloc() 和 free() 必须成对出现
5.基本操作-增/删
6.基本操作-查
顺序表
按值查找:O(n),若表内元素有序,可在O(log2n)时间内找到
链表
思路:先将链表一个一个的断开,再将断开的链表插入到原来的队列中
1.栈的定义
2.栈的基本操作
“创建&销毁”
“增&删”
1.顺序栈的定义
#define MaxSize 10 //定义栈中元素的最大个数
typedef struct{
ElemType data[MaxSize]; //静态数组存放栈中元素
int top; //栈顶元素
}SqStack;
void testStack(){
SqStack S; //声明一个顺序栈(分配空间)
//连续的存储空间大小为 MaxSize\*sizeof(ElemType)
}
2.顺序栈的基本操作
#define MaxSize 10 //定义栈中元素的最大个数 typedef struct{ ElemType data[MaxSize]; //静态数组存放栈中元素 int top; //栈顶元素 }SqStack; //初始化栈 void InitStack(SqStack &S){ S.top = -1; //初始化栈顶指针 } //判栈空 bool StackEmpty(SqStack S){ if(S.top == -1) //栈空 return true; else //栈不空 return false; } //新元素进栈 bool Push(SqStack &S, ElemType x){ if(S.top == MaxSize - 1) //栈满 return false; S.top = S.top + 1; //指针先加1 S.data[S.top] = x; //新元素入栈 /\* S.data[++S.top] = x; \*/ return true; } //出栈 bool Pop(SqStack &x, ElemType &x){ if(S.top == -1) //栈空 return false; x = S.data[S.top]; //先出栈 S.top = S.top - 1; //栈顶指针减1 return true; /\* x = S.data[S.top--]; \*/ //只是逻辑上的删除,数据依然残留在内存里 } //读栈顶元素 bool GetTop(SqStack S, ElemType &x){ if(S.top == -1) return false; x = S.data[S.top]; //x记录栈顶元素 return true; } void testStack(){ SqStack S; //声明一个顺序栈(分配空间) InitStack(S); //... }
**注意:**也可以初始化时定义 S.top = 0 :top指针指向下一个可以插入元素的位置(栈顶元素的后一个位置);
S.data[S.top++] = x;
3.共享栈
**定义:**利用栈底位置相对不变的特性,可以让两个顺序栈共享一个一维数组空间,将两个栈的栈底 分别设置在共享空间的两端,两个栈顶向共享空间的中间延伸。
#define MaxSize 10 //定义栈中元素的最大个数 typedef struct{ ElemType data[MaxSize]; //静态数组存放栈中元素 int top0; //0号栈栈顶指针 int top1; //1号栈栈顶指针 }ShStack; //初始化栈 void InitSqStack(ShStack &S){ S.top0 = -1; //初始化栈顶指针 S.top1 = MaxSize; }
栈满条件:top1-top0=1
1.定义:采用链式存储的栈称为链栈。
2.优点:链栈的优点是便于多个栈共享存储空间和提高其效率,且不存在栈满上溢的情况。
3.特点:
typedef struct Linknode{
ElemType data; //数据域
struct Linknode \*next; //指针域
}\*LiStack; //栈类型的定义
#include<stdio.h> struct Linknode{ int data; //数据域 Linknode \*next; //指针域 }Linknode,\*LiStack; typedef Linknode \*Node; //结点结构体指针变量 typedef Node List; //结点结构体头指针变量 //1. 初始化 void InitStack(LiStack &L){ //L为头指针 L = new Linknode; L->next = NULL; } //2.判栈空 bool isEmpty(LiStack &L){ if(L->next == NULL){ return true; } else return false; } //3. 进栈(:链栈基本上不会出现栈满的情况) void pushStack(LiStack &L, int x){ Linknode s; //创建存储新元素的结点 s = new Linknode; s->data = x; //头插法 s->next = L->next; L->next = s; } //4.出栈 bool popStack(LiStack &L, int &x){ Linknode s; if(L->next == NULL) //栈空不能出栈 return false; s = L->next; x = s->data; L->next = L->next->next; delete(s); return true; }
不带头结点的链栈基本操作:
#include<stdio.h> struct Linknode{ int data; //数据域 Linknode \*next; //指针域 }Linknode,\*LiStack; typedef Linknode \*Node; //结点结构体指针变量 typedef Node List; //结点结构体头指针变量 //1.初始化 void initStack(LiStack &L){ L=NULL; } //2.判栈空 bool isEmpty(LiStack &L){ if(L == NULL) return true; else teturn false; } //3.进栈 void pushStack(LiStack &L, int x){ Linknode s; //创建存储新元素的结点 s = new Linknode; s->next = L; L = s; } //4.出栈 bool popStack(LiStack &L, int &x){ Linknode s; if(L = NULL) //栈空不出栈 return false; s = L; x = s->data; L = L->next; delete(s); return true; }
1.定义:队列(Queue)简称队,是一种操作受限的线性表,只允许在表的一端进行插入,而在表的另一端进行删除。
2.特点
InitQueue(&Q):
初始化队列,构造一个空列表QDestroyQueue(&Q):
销毁队列,并释放队列Q所占用的内存空间EnQueue(&Q, x):
入队,若队列Q未满,将x加入,使之成为新的队尾DeQueue(&Q, &x):
出队,若队列Q非空,删除队头元素,并用x返回GetHead(Q,&x):
读队头元素,若队列Q非空,则将队头元素赋值给xQueueEmpty(Q):
判队列空,若队列Q为空,则返回//队列的顺序存储类型 # define MaxSize 10; //定义队列中元素的最大个数 typedef struct{ ElemType data[MaxSize]; //用静态数组存放队列元素 //连续的存储空间,大小为——MaxSize\*sizeof(ElemType) int front, rear; //队头指针和队尾指针 }SqQueue; //初始化队列 void InitQueue(SqQueue &Q){ //初始化时,队头、队尾指针指向0 Q.rear = Q.front = 0; } void test{ SqQueue Q; //声明一个队列 InitQueue(Q); //... } // 判空 bool QueueEmpty(SqQueue 0){ if(Q.rear == Q.front) //判空条件后 return true; else return false; }
a%b == a除以b的余数
初始:Q.front = Q.rear = 0;
队首指针进1:Q.front = (Q.front + 1) % MaxSize
队尾指针进1:Q.rear = (Q.rear + 1) % MaxSize —— 队尾指针后移,当移到最后一个后,下次移动会到第一个位置
队列长度:(Q.rear + MaxSize - Q.front) % MaxSize
区分队空还是队满的情况:
方案一: 牺牲一个单元来区分队空和队满
队尾指针的再下一个位置就是队头,即 (Q.rear+1)%MaxSize == Q.front
bool EnQueue(SqQueue &Q, ElemType 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, ElemType &x){
if(Q.rear == Q.front) //队空报错
return false;
x = Q.data[Q.front];
Q.front = (Q.front + 1) % MaxSize; //队头指针后移动
return true;
}
bool GetHead(SqQueue &Q, ElemType &x){
if(Q.rear == Q.front) //队空报错
return false;
x = Q.data[Q.front];
return true;
}
方案二: 不牺牲存储空间,设置size
定义一个变量 size
用于记录队列此时记录了几个数据元素,初始化 size = 0
,进队成功 size++
,出队成功size--
,根据size的值判断队满与队空
队满条件:size == MaxSize
队空条件:size == 0
# define MaxSize 10;
typedef struct{
ElemType data[MaxSize];
int front, rear;
int size; //队列当前长度
}SqQueue;
//初始化队列
void InitQueue(SqQueue &Q){
Q.rear = Q.front = 0;
size = 0;
}
方案三: 不牺牲存储空间,设置tag
定义一个变量 tag
,tag = 0
--最近进行的是删除操作;tag = 1
--最近进行的是插入操作;
每次删除操作成功时,都令tag = 0
;只有删除操作,才可能导致队空;
每次插入操作成功时,都令tag = 1
;只有插入操作,才可能导致队满;
队满条件:Q.front == Q.rear && tag == 1
队空条件:Q.front == Q.rear && tag == 0
# define MaxSize 10;
typedef struct{
ElemType data[MaxSize];
int front, rear;
int tag; //最近进行的是删除or插入
}SqQueue;
1.定义:队列的链式表示称为链队列,它实际上是一个同时带有队头指针和队尾指针的单链表。
链队列:用链表表示的队列,是限制仅在表头删除和表尾插入的单链表。
队列的链式存储类型可描述为:
typedef struct LinkNode{ //链式队列结点
ElemType data;
struct LinkNode \*next;
}
typedef struct{ //链式队列
LinkNode \*front, \*rear; //队列的队头和队尾指针
}LinkQueue;
2.链式队列的基本操作——带头结点
void InitQueue(LinkQueue &Q){
//初始化时,front、rear都指向头结点
Q.front = Q.rear = (LinkNode\*)malloc(sizeof(LinkNode));
Q.front -> next = NULL;
}
//判断队列是否为空
bool IsEmpty(LinkQueue Q){
if(Q.front == Q.rear) //也可用 Q.front -> next == NULL
return true;
else
return false;
}
//新元素入队 (表尾进行)
void EnQueue(LinkQueue &Q, ElemType x){
LinkNode \*s = (LinkNode \*)malloc(sizeof(LinkNode)); //申请一个新结点
s->data = x;
s->next = NULL; //s作为最后一个结点,指针域指向NULL
Q.rear->next = s; //新结点插入到当前的rear之后
Q.rear = s; //表尾指针指向新的表尾
}
//队头元素出队 bool DeQueue(LinkQueue &Q, ElemType &x){ if(Q.front == Q.rear) return false; //空队 LinkNode \*p = Q.front->next; //p指针指向即将删除的结点 (头结点所指向的结点) x = p->data; Q.front->next = p->next; //修改头结点的next指针 if(Q.rear == p) //此次是最后一个结点出队 Q.rear = Q.front; //修改rear指针 free(p); //释放结点空间 return true; }
链式存储:一般不会队满,除非内存不足
int length
记录链式队列长度//新元素入队 (表尾进行) void EnQueue(LinkQueue &Q, ElemType x){ LinkNode \*s = (LinkNode \*)malloc(sizeof(LinkNode)); //申请一个新结点 s->data = x; s->next = NULL; //第一个元素入队时需要特别处理 if(Q.front = NULL){ //在空队列中插入第一个元素 Q.front = s; //修改队头队尾指针 Q.rear = s; }else{ Q.rear->next = s; //新结点插入到rear结点之后 Q.rear = s; //修改rear指针指向新的表尾结点 } }
1.定义:双端队列是指允许两端都可以进行入队和出队操作的队列
利用一组地址连续的存储单元依次存放队列中的数据元素。因为队头和队尾的位置是变化的。所以:设头、尾指针。
求循环队列的长度:
用栈实现括号匹配
代码:
#define MaxSize 10 typedef struct{ char data[MaxSize]; int top; } SqStack; //初始化栈 InitStack(SqStack &S) //判断栈是否为空 bool StackEmpty(SqStack &S) //新元素入栈 bool Push(SqStack &S, char x) //栈顶元素出栈,用x返回 bool Pop(SqStack &S, char &x) bool bracketCheck(char str[], int length){ SqStack S; //声明 InitStack(S); //初始化栈 for(int i=0; i<length; i++){ if(str[i] == '(' || str[i] == '[' || str[i] == '{'){ Push(S, str[i]); //扫描到左括号,入栈 }else{ if(StackEmpty(S)) //扫描到右括号,且当前栈空 return false; //匹配失败 char topElem; //存储栈顶元素 Pop(S, topElem); //栈顶元素出栈 if(str[i] == ')' && topElem != '(' ) return false; if(str[i] == ']' && topElem != '[' ) return false; if(str[i] == '}' && topElem != '{' ) return false; } } StackEmpty(S); //栈空说明匹配成功 }
1. 中缀表达式 (需要界限符)
运算符在两个操作数中间:
① a + b
② a + b - c
③ a + b - c\*d
④ ((15 ÷ (7-(1+1)))×3)-(2+(1+1))
⑤ A + B × (C - D) - E ÷ F
2. 后缀表达式 (逆波兰表达式)
运算符在两个操作数后面:
① a b +
② ab+ c - / a bc- +
③ ab+ cd\* -
④ 15 7 1 1 + - ÷ 3 × 2 1 1 + + -
⑤ A B C D - × + E F ÷ - (机算结果)
A B C D - × E F ÷ - + (不选择)
中缀表达式转后缀表达式-手算
步骤1: 确定中缀表达式中各个运算符的运算顺序
步骤2: 选择下一个运算符,按照[左操作数 右操作数 运算符]的方式组合成一个新的操作数
步骤3: 如果还有运算符没被处理,继续步骤2
“左优先”原则: 只要左边的运算符能先计算,就优先算左边的 (保证运算顺序唯一);
中缀:A + B - C \* D / E + F
① ④ ② ③ ⑤
后缀:A B + C D \* E / - F +
重点:中缀表达式转后缀表达式-机算
初始化一个栈,用于保存暂时还不能确定运算顺序的运算符。从左到右处理各个元素,直到末尾。可能遇到三种情况:
遇到操作数: 直接加入后缀表达式。
遇到界限符: 遇到 ‘(’ 直接入栈; 遇到 ‘)’ 则依次弹出栈内运算符并加入后缀表达式,直到弹出 ‘(’ 为止。注意: '(' 不加入后缀表达式。
遇到运算符: 依次弹出栈中优先级高于或等于当前运算符的所有运算符,并加入后缀表达式,若碰到 ‘(’ 或栈空则停止。之后再把当前运算符入栈。
按上述方法处理完所有字符后,将栈中剩余运算符依次弹出,并加入后缀表达式。
后缀表达式的计算—手算:
从左往右扫描,每遇到一个运算符,就让运算符前面最近的两个操作数执行对应的运算,合体为一个操作数;
注意: 两个操作数的左右顺序
重点:后缀表达式的计算—机算
用栈实现后缀表达式的计算(栈用来存放当前暂时不能确定运算次序的操作数)
步骤1: 从左往后扫描下一个元素,直到处理完所有元素;
步骤2: 若扫描到操作数,则压入栈,并回到步骤1;否则执行步骤3;
步骤3: 若扫描到运算符,则弹出两个栈顶元素,执行相应的运算,运算结果压回栈顶,回到步骤1;
注意: 先出栈的是“右操作数”
3.前缀表达式 (波兰表达式)
运算符在两个操作数前面:
① + a b
② - +ab c
③ - +ab \*cd
中缀表达式转前缀表达式—手算
步骤1: 确定中缀表达式中各个运算符的运算顺序
步骤2: 选择下一个运算符,按照[运算符 左操作数 右操作数]的方式组合成一个新的操作数
步骤3: 如果还有运算符没被处理,就继续执行步骤2
“右优先”原则: 只要右边的运算符能先计算,就优先算右边的;
中缀:A + B \* (C - D) - E / F
⑤ ③ ② ④ ①
前缀:+ A - \* B - C D / E F
前缀表达式的计算—机算
用栈实现前缀表达式的计算
步骤1: 从右往左扫描下一个元素,直到处理完所有元素;
步骤2: 若扫描到操作数则压入栈,并回到步骤1,否则执行步骤3
步骤3: 若扫描到运算符,则弹出两个栈顶元素,执行相应运算,运算结果压回栈顶,回到步骤1;
注意: 先出栈的是“左操作数”
4.中缀表达式的计算(用栈实现)
两个算法的结合: 中缀转后缀 + 后缀表达式的求值
初始化两个栈,操作数栈 和运算符栈
若扫描到操作数,压人操作数栈
若扫描到运算符或界限符,则按照“中缀转后缀”相同的逻辑压入运算符栈 (期间也会弹出运算符,每当弹出一个运算符时,就需要再弹出两个操作数栈的栈项元素并执行相应运算,运算结果再压回操作数栈)
函数调用的特点:最后被调用的函数最先执行结束(LIFO)
函数调用时,需要用一个栈存储:
递归调用时,函数调用栈称为 “递归工作栈”:
**缺点:**太多层递归可能回导致栈溢出;
适合用“递归”算法解决:可以把原始问题转换为属性相同,但规模较小的问题;
矩阵定义: 一个由m*n个元素排成的m行(横向)n列(纵向)的表。
矩阵的常规存储:将矩阵描述为一个二维数组。
1.一维数组
Elemtype a[10];
各数组元素大小相同,物理上连续存放;
起始地址:LOC
数组下标:默认从0开始!
数组元素 a[i]
的存放地址 = LOC + i × sizeof(ElemType)
2.二维数组
Elemtype b[2][4]; //2行4列的二维数组
行优先/列优先存储优点:实现随机存储
起始地址:LOC
M行N列的二维数组 b[M][N]
中,b[i][j]
的存储地址:
行优先存储: LOC + (i×N + j) × sizeof(ElemType)
列优先存储:LOC + (j×M + i) × sizeof(ElemType)
二维数组存储:
特殊矩阵——压缩存储空间(只存有用的数据)
矩阵的压缩存储:为多个相同的非零元素只分配一个存储空间;对零元素不分配空间。
在一个n阶方阵A中,若元素满足下述性值:
则称A为对称矩阵。
S = 'iPhone 11 Pro Max?';
假设有串 T = ''
, S = 'iPhone 11 Pro Max?'
, W = 'Pro'
StrAssign(&T, chars)
: 赋值操作,把串T赋值为chars;StrCopy(&T, S)
: 复制操作,把串S复制得到串TStrEmpty(S)
: 判空操作,若S为空串,则返回TRUE,否则返回False;StrLength(S)
: 求串长,返回串S的元素个数;ClearString(&S)
: 清空操作,将S清为空串;DestroyString(&S)
: 销毁串,将串S销毁——回收存储空间;Concat(&T, S1, S2)
: 串联联接,用T返回由S1和S2联接而成的新串———可能会导致存储空间的扩展;Concat(&T, S, W)
T = ‘iPhone 11 Pro Max?Pro’
SubString(&Sub, S, pos, len)
: 求子串,用Sub返回串S的第pos个字符起长度为len的子串;SubString(&T, S, 4, 6)
T = ‘one 11’
Index(S, T)
: 定位操作,若主串S中存在与串T值相同的子串,则返回它再主串S中第一次出现的位置,否则函数值为0;StrCompare(S, T):
串的比较操作,参照英文词典排序方式;若S > T,返回值>0; S = T,返回值=0 (需要两个串完全相同) ; S < T,返回值<0;1定长顺序存储表示
#define MAXLEN 255 //预定义最大串长为255
typedef struct{
char ch[MAXLEN]; //静态数组实现(定长顺序存储)
//每个分量存储一个字符
//每个char字符占1B
int length; //串的实际长度
}SString;
#define MAXLEN 255 typedef struct{ char ch[MAXLEN]; int length; }SString; // 1. 求子串 bool SubString(SString &Sub, SString S, int pos, int len){ //子串范围越界 if (pos+len-1 > S.length) return false; for (int i=pos; i<pos+len; i++) Sub.cn[i-pos+1] = S.ch[i]; Sub.length = len; return true; } // 2. 比较两个串的大小 int StrCompare(SString S, SString T){ for (int i; i<S.length && i<T.length; i++){ if(S.ch[i] != T.ch[i]) return S.ch[i] - T.ch[i]; } //扫描过的所有字符都相同,则长度长的串更大 return S.length - T.length; } // 3. 定位操作 int Index(SString S, SString T){ int i=1; n = StrLength(S); m = StrLength(T); SString sub; //用于暂存子串 while(i<=n-m+1){ SubString(Sub,S,i,m); if(StrCompare(Sub,T)!=0) ++i; else return i; // 返回子串在主串中的位置 } return 0; //S中不存在与T相等的子串 }
2.堆分配存储表示
**堆存储结构的特点:**仍以一组空间足够大的、地址连续的存储单元依次存放字符序列,但它们的存储空间实在程序执行过程种动态分配的 。
通常,C语言提供的串类型就是以这种存储方式实现的。由动态分配函数malloc()分配一块实际串长所需要的存储空间(“堆”),如果分配成功,则返回此空间的起始地址,作为串的基址。由free()释放串不再需要的空间,
**堆存储结构的优点:**堆存储结构既有顺序存储结构的特点,处理(随机取子串)方便,操作中对串长又没有任何限制,更显灵活,因此在串处理的应用程序中常被采用。
//动态数组实现
typedef struct{
char \*ch; //按串长分配存储区,ch指向串的基地址
int length; //串的长度
}HString;
HString S;
S.ch = (char \*) malloc(MAXLINE \* sizeof(char)); //基地址指针指向连续空间的起始位置
//malloc()需要手动free()
S.length;
3.串的链式存储
typedef struct StringNode{
char ch; //每个结点存1个字符
struct StringNode \*next;
}StringNode, \* String;
问题:存储密度低,每个字符1B,每个指针4B;
解决方案:每一个链表的结点存储多个字符——每个结点称为块——块链结构
typedef struct StringNode{
char ch[4]; //每个结点存多个个字符
struct StringNode \*next;
}StringNode, \* String;
结合链表思考优缺点
模式匹配:子串的定位操作称为串的模式,它求的是子串(常称模式串)在主串中的位置。
int Index(SString S, SString T){ int i=1; //扫描主串S int j=1; //扫描模式串T while(i<=S.length && j<=T.length){ if(S.ch[i] == T.ch[j]){ ++i; ++j; //继续比较后继字符 } else{ i = i-j+2; j=1; //指针后退重新开始匹配 } } if(j>T.length) return i-T.length; else return 0; }
时间复杂度分析:
n-m+1
个子串O(nm)
O((n-m+1)m) = O(nm - m^2 + m) = O(nm)
O(n)
O(n-m+1) = O(n)
'abaabc'
next数组
进行模式匹配int Index\_KMP(SString S, SString T, int next[]){ int i=1; //主串 int j=1; //模式串 while(i<S.length && j<=T.length){ if(j==0 || S.ch[i]==T.ch[j]){ //第一个元素匹配失败时 ++j; ++i; //继续比较后继字符 } else j=next[j] //模式串向右移动 } if(j>T.length) return i-T.length; //匹配成功 }
next数组的求法:
我们能确定next数组第一二位一定分别为0,1,后面求解每一位的next值时,根据前一位进行比较。
从第三位开始,将前一位与其next值对应的内容进行比较,
如果相等,则该位的next值就是前一位的next值加上1;
如果不等,向前继续寻找next值对应的内容来与前一位进行比较,
直到找到某个位上内容的next值对应的内容与前一位相等为止,
则这个位对应的值加上1即为需求的next值;
如果找到第一位都没有找到与前一位相等的内容,那么求解的位上的next值为1。
注意下标都是从1开始的
传送门:https://blog.csdn.net/m0_37482190/article/details/86667059
树是n个结点的有限集。
定义:
二叉树是n(n>=0)个结点的有限集,它或者是空集(n=0),或者由一个根结点及两颗互不相交的分别称作这个根的左子树和右子树的二叉树组成。
特点:
二叉树的性质:
1.在二叉树的第i层上至多有2^(i-1)个结点(i>1)。
2.深度为k的二叉树至多有2^k-1个结点(k>=1)。
3.对任何一颗二叉树T,如果其叶子数为n0,度为2的结点数为n2,则n0=n2+1.
4. 具有n个结点的完全二叉树的深度为(log2N)+1。
5.
注意:二叉树不是树的特殊情况,它们是两个概念。
#define MaxSize 100
struct TreeNode{
ElemType value; //结点中的数据元素
bool isEmpty; //结点是否为空
}
main(){
TreeNode t[MaxSize];
for (int i=0; i<MaxSize; i++){
t[i].isEmpty = true;
}
}
//二叉树的结点 struct ElemType{ int value; }; typedef struct BiTnode{ ElemType data; //数据域 struct BiTNode \*lchild, \*rchild; //左、右孩子指针 }BiTNode, \*BiTree; //定义一棵空树 BiTree root = NULL; //插入根节点 root = (BiTree) malloc (sizeof(BiTNode)); root -> data = {1}; root -> lchild = NULL; root -> rchild = NULL; //插入新结点 BiTNode \*p = (BiTree) malloc (sizeof(BiTNode)); p -> data = {2}; p -> lchild = NULL; p -> rchild = NULL; root -> lchild = p; //作为根节点的左孩子
typedef struct BiTnode{
ElemType data; //数据域
struct BiTNode \*lchild, \*rchild; //左、右孩子指针
struct BiTNode \*parent; //父节点指针
}BiTNode, \*BiTree;
typedef struct BiTnode{
ElemType data;
struct BiTNode \*lchild, \*rchild;
}BiTNode, \*BiTree;
void PreOrder(BiTree T){
if(T!=NULL){
visit(T); //访问根结点
PreOrder(T->lchild); //递归遍历左子树
PreOrder(T->rchild); //递归遍历右子树
}
}
typedef struct BiTnode{
ElemType data;
struct BiTNode \*lchild, \*rchild;
}BiTNode, \*BiTree;
void InOrder(BiTree T){
if(T!=NULL){
InOrder(T->lchild); //递归遍历左子树
visit(T); //访问根结点
InOrder(T->rchild); //递归遍历右子树
}
}
typedef struct BiTnode{
ElemType data;
struct BiTNode \*lchild, \*rchild;
}BiTNode, \*BiTree;
void PostOrder(BiTree T){
if(T!=NULL){
PostOrder(T->lchild); //递归遍历左子树
PostOrder(T->rchild); //递归遍历右子树
visit(T); //访问根结点
}
}
//二叉树的结点(链式存储) typedef struct BiTnode{ ElemType data; struct BiTNode \*lchild, \*rchild; }BiTNode, \*BiTree; //链式队列结点 typedef struct LinkNode{ BiTNode \* data; typedef LinkNode \*next; }LinkNode; typedef struct{ LinkNode \*front, \*rear; }LinkQueue; //层序遍历 void LevelOrder(BiTree T){ LinkQueue Q; InitQueue (Q); //初始化辅助队列 BiTree p; EnQueue(Q,T); //将根节点入队 while(!isEmpty(Q)){ //队列不空则循环 DeQueue(Q,p); //队头结点出队 visit(p); //访问出队结点 if(p->lchild != NULL) EnQueue(Q,p->lchild); //左孩子入队 if(p->rchild != NULL) EnQueue(Q,p->rchild); //右孩子入队 } }
//线索二叉树结点
typedef struct ThreadNode{
ElemType data;
struct ThreadNode \*lchild, \*rchild;
int ltag, rtag; // 左、右线索标志
}ThreadNode, \*ThreadTree;
tag == 0: 指针指向孩子
tag == 1: 指针是“线索”
typedef struct ThreadNode{ int data; struct ThreadNode \*lchild, \*rchild; int ltag, rtag; // 左、右线索标志 }ThreadNode, \*ThreadTree; //全局变量pre, 指向当前访问的结点的前驱 TreadNode \*pre=NULL; void InThread(ThreadTree T){ if(T!=NULL){ InThread(T->lchild); //中序遍历左子树 visit(T); //访问根节点 InThread(T->rchild); //中序遍历右子树 } } void visit(ThreadNode \*q){ if(q->lchid = NULL){ //左子树为空,建立前驱线索 q->lchild = pre; q->ltag = 1; } if(pre!=NULL && pre->rchild = NULL){ pre->rchild = q; //建立前驱结点的后继线索 pre->rtag = 1; } pre = q; } //中序线索化二叉树T void CreateInThread(ThreadTree T){ pre = NULL; //pre初始为NULL if(T!=NULL);{ //非空二叉树才能进行线索化 InThread(T); //中序线索化二叉树 if(pre->rchild == NULL) pre->rtag=1; //处理遍历的最后一个结点 } }
typedef struct ThreadNode{ int data; struct ThreadNode \*lchild, \*rchild; int ltag, rtag; // 左、右线索标志 }ThreadNode, \*ThreadTree; //全局变量pre, 指向当前访问的结点的前驱 TreadNode \*pre=NULL; //先序遍历二叉树,一边遍历一边线索化 void PreThread(ThreadTree T){ if(T!=NULL){ visit(T); if(T->ltag == 0) //lchild不是前驱线索 PreThread(T->lchild); PreThread(T->rchild); } } void visit(ThreadNode \*q){ if(q->lchid = NULL){ //左子树为空,建立前驱线索 q->lchild = pre; q->ltag = 1; } if(pre!=NULL && pre->rchild = NULL){ pre->rchild = q; //建立前驱结点的后继线索 pre->rtag = 1; } pre = q; } //先序线索化二叉树T void CreateInThread(ThreadTree T){ pre = NULL; //pre初始为NULL if(T!=NULL);{ //非空二叉树才能进行线索化 PreThread(T); //先序线索化二叉树 if(pre->rchild == NULL) pre->rtag=1; //处理遍历的最后一个结点 } }
typedef struct ThreadNode{ int data; struct ThreadNode \*lchild, \*rchild; int ltag, rtag; // 左、右线索标志 }ThreadNode, \*ThreadTree; //全局变量pre, 指向当前访问的结点的前驱 TreadNode \*pre=NULL; //先序遍历二叉树,一边遍历一边线索化 void PostThread(ThreadTree T){ if(T!=NULL){ PostThread(T->lchild); PostThread(T->rchild); visit(T); //访问根节点 } } void visit(ThreadNode \*q){ if(q->lchid = NULL){ //左子树为空,建立前驱线索 q->lchild = pre; q->ltag = 1; } if(pre!=NULL && pre->rchild = NULL){ pre->rchild = q; //建立前驱结点的后继线索 pre->rtag = 1; } pre = q; } //先序线索化二叉树T void CreateInThread(ThreadTree T){ pre = NULL; //pre初始为NULL if(T!=NULL);{ //非空二叉树才能进行线索化 PostThread(T); //后序线索化二叉树 if(pre->rchild == NULL) pre->rtag=1; //处理遍历的最后一个结点 } }
若 p->rtag == 1, 则 next = p->rchild;
若 p->rtag == 0, 则 p 必有右孩子, 则 next = p的右子树中最左下结点;
//1. 找到以P为根的子树中,第一个被中序遍历的结点 ThreadNode \*Firstnode(ThreadNode \*p){ //循环找到最左下的结点(不一定是叶结点) while(p->ltag == 0) p=p->lchild; return p; } //2. 在中序线索二叉树中找到结点p的后继结点 ThreadNode \*Nextnode(ThreadNode \*p){ //右子树最左下结点 if(p->rtag==0) return Firstnode(p->rchild); else return p->rchild; //rtag==1,直接返回后继线索 } //3. 对中序线索二叉树进行中序遍历 void Inorder(ThreadNode \*T){ //T为根节点指针 for(ThreadNode \*p = Firstnode(T); p!=NULL; p = Nextnode(p)) visit(p); }
若
p->rtag == 1, 则 next = p->rchild; 若 p->rtag == 0
, 则 p 必有右孩子(左孩子不知道)case1: 若p有左孩子 ——— 根 左 右 / 根 (根 左 右) 右
case2: 若p没有左孩子 ——— 根 右 / 根 (*根 *左 右)
若 p->ltag == 1, 则 next = p->lchild;
若 p->ltag == 0, 则 p
必有左孩子,但是先序遍历中,左右子树的结点只可能是根的后继,不可能是前驱,所以不能从左右孩子里寻找p的先序前驱,(除非从头开始遍历/三叉链表case1: 如果能够找到p的父节点,且p是左孩子 —— p的父节点就是p的前驱;
case2: 如果能够找到p的父节点,且p是右孩子,且其左兄弟为空 —— p的父节点就是p的前驱;
case3: 如果能够找到p的父节点,且p是右孩子,且其左兄弟非空 ——
p的前驱为左兄弟子树中最后一个被先序遍历到的结点(根节点出发,先往右,右没有往左,找到最下一层的结点);case4: p没有父节点,即p为根节点,则p没有先序前驱
若 p->ltag == 1, 则 next = p->lchild;
若 p->ltag == 0, 则 p 必有左孩子(不知道有没有右孩子)
case1: 若p有右孩子 ——— 左 右 根 / 左 (左 右 根) 根
case2: 若p没有右孩子 ——— 左 根 (左子树按后序遍历,最后一个结点,p的左孩子)
若 p->rtag == 1, 则 next = p->rchild;
若 p->rtag == 0, 则 p 必有右孩子, 左孩子不知道, 但是在后序遍历中,左右子树中的结点只有可能是根的前驱,而不可能是根的后继,所以找不到后继,(除非从头开始遍历/三叉链表
case1: 如果能找到p的父节点,且p是右孩子 —— p的父节点即为其后继
case2: 如果能找到p的父节点,且p是左孩子,其右兄弟为空 —— p的父节点即为其后继
case3: 如果能找到p的父节点,且p是左孩子,其右兄弟非空 —— p的后继为其右兄弟子树中第一个被后序遍历的结点;
case4: p没有父节点,即p为根节点,则p没有后序后继;
数据域:存放结点本身信息。
双亲域:指示本结点的双亲结点在数组中的位置。
#define MAX\_TREE\_SIZE 100 //树中最多结点数
typedef struct{ //树的结点定义
ElemType data;
int parent; //双亲位置域
}PTNode;
typedef struct{ //树的类型定义
PTNode nodes[MAX_TREE_SIZE]; //双亲表示
int n; //结点数
}PTree;
增:新增数据元素,无需按逻辑上的次序存储;(需要更改结点数n)
删(叶子结点):① 将伪指针域设置为-1;②用后面的数据填补;(需要更改结点数n)
查询:①优点-查指定结点的双亲很方便;②缺点-查指定结点的孩子只能从头遍历,空数据导致遍历更慢;
struct CTNode{ int child; //孩子结点在数组中的位置 struct CTNode \*next; // 下一个孩子 }; typedef struct{ ElemType data; struct CTNode \*firstChild; // 第一个孩子 }CTBox; typedef struct{ CTBox nodes[MAX_TREE_SIZE]; int n, r; // 结点数和根的位置 }CTree;
typedef struct CSNode{
ElemType data; //数据域
struct CSNode \*firstchild, \*nextsibling; //第一个孩子和右兄弟指针, \*firstchild 看作左指针,\*nextsibling看作右指针
}CSNode. \*CSTree;
本质:森林中各个树的根结点之间视为兄弟关系
将树转换成二叉树:
void PreOrder(TreeNode \*R){
if(R!=NULL){
visit(R); //访问根节点
while(R还有下一个子树T)
PreOrder(T); //先跟遍历下一个子树
}
}
void PostOrder(TreeNode \*R){
if(R!=NULL){
while(R还有下一个子树T)
PostOrder(T); //后跟遍历下一个子树
visit(R); //访问根节点
}
}
若树非空,则根结点入队;
若队列非空,队头元素出队并访问,同时将该元素的孩子依次入队;
重复以上操作直至队尾为空;
typedef struct BSTNode{ int key; struct BSTNode \*lchild, \*rchild; }BSTNode, \*BSTree; //在二叉排序树中查找值为key的结点(非递归) //最坏空间复杂度:O(1) BSTNode \*BST\_Search(BSTree T, int key){ while(T!=NULL && key!=T->key){ //若树空或等于跟结点值,则结束循环 if(key<T->key) //值小于根结点值,在左子树上查找 T = T->lchild; else //值大于根结点值,在右子树上查找 T = T->rchild; } return T; } //在二叉排序树中查找值为key的结点(递归) //最坏空间复杂度:O(h) BSTNode \*BSTSearch(BSTree T, int key){ if(T == NULL) return NULL; if(Kry == T->key) return T; else if(key < T->key) return BSTSearch(T->lchild, key); else return BSTSearch(T->rchild, key); }
//在二叉排序树中插入关键字为k的新结点(递归) //最坏空间复杂度:O(h) int BST\_Insert(BSTree &T, int k){ if(T==NULL){ //原树为空,新插入的结点为根结点 T = (BSTree)malloc(sizeof(BSTNode)); T->key = k; T->lchild = T->rchild = NULL; return 1; //插入成功 } else if(K == T->key) //树中存在相同关键字的结点,插入失败 return 0; else if(k < T->key) return BST\_Insert(T->lchild,k); else return BST\_Insert(T->rchild,k); }
//按照str[]中的关键字序列建立二叉排序树
void Crear\_BST(BSTree &T, int str[], int n){
T = NULL; //初始时T为空树
int i=0;
while(i<n){
BST\_Insert(T,str[i]); //依次将每个关键字插入到二叉排序树中
i++;
}
}
查找长度:查找运算中,需要对比关键字的次数,反映了查找操作时间复杂度;
查找成功的平均查找长度ASL
查找失败的平均查找长度ASL
//平衡二叉树结点
typedef struct AVLNode{
int key; //数据域
int balance; //平衡因子
struct AVLNode \*lchild; \*rchild;
}AVLNode, \*AVLTree;
LL: 在A结点的左孩子的左子树中插入导致不平衡
调整: A的左孩子结点右上旋
RR: 在A结点的右孩子的右子树中插入导致不平衡
调整: A的右孩子结点左上旋
LR: 在A结点的左孩子的右子树中插入导致不平衡
调整: A的左孩子的右孩子,先左上旋再右上旋
RL: 在A结点的右孩子的左子树中插入导致不平衡
调整: A的右孩子的左孩子,先右上旋再左上旋
:由哈夫曼树得到的二进制前缀编码称为哈夫曼编码。
定义:从图的任意指定顶点出发,依照某种规则去访问图中所有顶点,且每个顶点仅被访问一次,这一过程叫图的遍历。
方式:
普里姆(Prim)算法。
迪杰斯特拉:
void InsertSort(int A[], int n){ //A中共n个数据元素
int i,j,temp;
for(i=1; i<n; i++)
if(A[i]<A[i-1]){ //A[i]关键字小于前驱
temp = A[i];
for(j=i-1; j>=0 && A[j]>temp; --j)
A[j+1] = A[j]; //所有大于temp的元素都向后挪
A[j+1] = temp; //复制到插入位置
}
}
void InsertSort(int A[], int n){ //A中从1开始存储,0放哨兵
int i,j;
for(i=1; i<n; i++)
if(A[i]<A[i-1]){
A[0] = A[i]; //复制为哨兵
for(j=i-1; A[0] < A[j]; --j) //从后往前查找待插入位置
A[j+1] = A[j]; //向后挪动
A[j+1] = A[0]; //复制到插入位置
}
}
空间复杂度:O(1)
时间复杂度:主要来自于对比关键字、移动关键字,若有n个元素,则需要n-1躺处理
最好情况: 原本为有序,共n-1趟处理,每一趟都只需要对比1次关键字,不需要移动元素,共对比n-1次 —— O(n)
最差情况: 原本为逆序 —— O(n²)
平均情况: O(n²)
算法稳定性:稳定
void InsertSort(int A[], int n){ int i,j,low,high,mid; for(i=2;i<=n;i++){ A[0] = A[i]; //将A[i]暂存到A[0] low = 1; high = i-1; //折半查找的范围 while(low<=high){ //折半查找 mid = (low + high)/2; //取中间点 if(A[mid]>A[0]) //查找左半子表 high = mid - 1; else //查找右半子表 low = mid + 1; ![img](https://img-blog.csdnimg.cn/img_convert/380b33250eee4e7ddf0463d175c39721.png) ![img](https://img-blog.csdnimg.cn/img_convert/cede0e017b32df60cafaadcb32b7946d.png) ![img](https://img-blog.csdnimg.cn/img_convert/8ce1bb371c2171c60949df89528bd757.png) **既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上软件测试知识点,真正体系化!** **由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新** **[需要这份系统化的资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618608311)** bmhlaQ,shadow_50,text_Q1NETiBA5rGg6bG85LmL5q6D,size_19,color_FFFFFF,t_70,g_se,x_16) ### 第八章 排序 #### 8.1排序的基本概念 1. 排序:重新排列表中的元素,使表中元素满足按关键字有序的过程(关键字可以相同) 2. 排序算法的评价指标:时间复杂度、空间复杂度; 3. 排序算法的稳定性:关键字相同的元素在排序之后相对位置不变,称为稳定的;(选择题考查) Q: 稳定的排序算法一定比不稳定的好? A: 不一定,要看实际需求; 4. 排序算法的分类: **内部排序:** 数据都在内存——关注如何使时间、空间复杂度更低; **外部排序:** 数据太多,无法全部放入内存——关注如何使时间、空间复杂度更低,如何使读/写磁盘次数更少; #### 8.2 插入排序 ##### 8.2.1直接插入排序 1. **算法思想:** 每次将一个待排序的记录按其关键字大小,插入(依次对比、移动)到前面已经排好序的子序列中,直到全部记录插入完成 2. **代码实现:** * 不带“哨兵”
void InsertSort(int A[], int n){ //A中共n个数据元素
int i,j,temp;
for(i=1; i<n; i++)
if(A[i]<A[i-1]){ //A[i]关键字小于前驱
temp = A[i];
for(j=i-1; j>=0 && A[j]>temp; --j)
A[j+1] = A[j]; //所有大于temp的元素都向后挪
A[j+1] = temp; //复制到插入位置
}
}
* 带“哨兵” ,优点:不用每轮循环都判断j>=0
void InsertSort(int A[], int n){ //A中从1开始存储,0放哨兵
int i,j;
for(i=1; i<n; i++)
if(A[i]<A[i-1]){
A[0] = A[i]; //复制为哨兵
for(j=i-1; A[0] < A[j]; --j) //从后往前查找待插入位置
A[j+1] = A[j]; //向后挪动
A[j+1] = A[0]; //复制到插入位置
}
}
3. 算法效率分析 > > 空间复杂度:O(1) > 时间复杂度:主要来自于对比关键字、移动关键字,若有n个元素,则需要n-1躺处理 > 最好情况: 原本为有序,共n-1趟处理,每一趟都只需要对比1次关键字,不需要移动元素,共对比n-1次 —— O(n) > 最差情况: 原本为逆序 —— O(n²) > 平均情况: O(n²) > 算法稳定性:稳定 > > > 4. 对链表进行插入排序 移动元素的次数变少了,因为只需要修改指针,不需要依次右移; 但是关键字对比的次数依然是O(n²)数量级,因此整体看来时间复杂度仍然是O(n²) ##### 8.2.2折半插入排序 1. 思路: 先用折半查找找到应该插入的位置,再移动元素; 2. 为了保证稳定性,当查找到和插入元素关键字一样的元素时,应该继续在这个元素的右半部分继续查找以确认位置; 即当 A[mid] == A[0] 时,应继续在mid所指位置右边寻找插入位置 3. 当low>high时,折半查找停止,应将[low,i-1]or[high+1,i-1]内的元素全部右移,并将A[0]复制到low所指的位置; 4. 代码实现
void InsertSort(int A[], int n){
int i,j,low,high,mid;
for(i=2;i<=n;i++){
A[0] = A[i]; //将A[i]暂存到A[0]
low = 1; high = i-1; //折半查找的范围
while(low<=high){ //折半查找
mid = (low + high)/2; //取中间点
if(A[mid]>A[0]) //查找左半子表
high = mid - 1;
else //查找右半子表
low = mid + 1;
[外链图片转存中…(img-Wps5BlI3-1715006184155)]
[外链图片转存中…(img-XMJlcBn9-1715006184155)]
[外链图片转存中…(img-VZHL7Lrk-1715006184155)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上软件测试知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。