// 相关操作状态定义
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
typedef int Status; // Status 是函数的类型,其值是函数结果状态码,如 OK 等
/\*\*
获取线性表中第 i 个元素
@param L 线性表 L, 必须已存在
@param i 要获取的编号, 需满足 i ≤ i ≤ ListLength(L)
@param e 对应元素返回
@return 操作结果 OK/ERROR
\*/
Status GetSqListElem(SqList L, int i, ElemType \*e) {
if (L.length==0 || i<1 || i>L.length) return ERROR;
\*e = L.data[i-1];
return OK;
}
插入操作
/\*\*
向线性表的第 i 个位置插入元素
@param L 线性表, 必须存在
@param i 位置编号, 需满足 i ≤ i ≤ ListLength(L)
@param e 要插入的元素
@return 操作结果 OK/ERROR
\*/
Status SqListInsert(SqList \*L, int i, ElemType e) {
// 1. 顺序线性表已满
if (L->length == MAXSIZE) return ERROR;
// 2. 插入位置i 不在线性表范围内
if (i<1 || i>L->length+1) return ERROR;
// 插入数据位置不在表尾部
if (i <= L->length) {
// 3.将要插入位置后的元素统一后移一位
for (int k=L->length-1; k>=i-1; k--) {
L->data[k+1] = L->data[k];
}
}
// 4.将要插入的元素 填入 第i个位置
L->data[i-1] = e;
// 5. 表长+1
L->length ++;
return OK;
}
删除操作
/\*\*
删除线性表第 i 个位置的元素
@param L 线性表, 必须存在
@param i 要删除的位置编号, 需满足 i ≤ i ≤ ListLength(L)
@param e 被删除的元素返回
@return 操作结果 OK/ERROR
\*/
Status SqListDelete(SqList \*L, int i, ElemType \*e) {
// 1. 空表
if (L->length==0) return ERROR;
// 2. 要删除的位置不正确
if (i<1 || i>L->length) return ERROR;
\*e = L->data[i-1];
// 3. 若删除的不是表尾, 将删除位置 后继元素 前移
if (i<L->length) {
for (int k=i; k<L->length; k++) {
L->data[k-1] = L->data[k]; // 依次前移
}
}
// 4. 表长-1
L->length --;
return OK;
}
线性表的链式存储结构
线性表链式存储结构的定义
为了表示每个数据元素 ai 与其直接后继元素 ai+1 之间的逻辑关系,对数据元素 ai 来说,出了存储其本身的信息之外,还需存储一个指示其直接后继的信息(即直接后继的存储位置)。把存储数据元素信息的域称为数据域,把存储直接后继位置的域称为指针域。指针域中存储的信息乘坐指针或链。这两部分信息组成数据元素 ai 的存储映像,称为结点(Node)。
n 个节点(ai的存储映像)链接成一个链表,即为线性表 (a1, a2, …, an) 的连式存储结构,因为此链表的每个节点中只包含一个指针域,所以叫做单链表。
链表中第一个节点的存储位置叫做头指针
有时,为了更加方便的对链表进行操作,会在单链表的第一个结点前附设一个结点,称为头结点
头指针和头结点的异同
线性表链式存储结构代码描述
/\*\*
线性表的单链表的存储结构
结点由存放数据元素的数据域和存放后继结点地址的指针域组成
\*/
typedef struct Node {
ElemType data; // 数据信息
struct Node \*next; // 指向信息/单线联系
} Node;
// 定义 LinkList
typedef struct Node \*LinkList;
单链表的读取
/\*\*
获取单链表第i 个位置的元素
@param L 单链表, 必须存在
@param i 要获取元素的位置标号 1 ≤ i ≤ ListLength(L)
@param e 返回元素
@return 操作是否成功 OK/ERROR
\*/
Status GetLinkListElem(LinkList L, int i, ElemType \*e) {
LinkList p = L->next;
int j = 1;
while (p && j<i)
{
p = p->next; // 后继
++j;
}
if (!p || j > i) {
return ERROR;
}
\*e = p->data;
return OK;
}
单链表的插入与删除
单链表的插入
/\*\*
向单链表第 i 个位置插入元素
@param L 单链表, 必须存在
@param i 要插入的位置标号, 1 ≤ i ≤ ListLength(L)
@param e 要插入的元素
@return 操作结果 OK/ERROR
\*/
Status LinkListInsert(LinkList \*L, int i, ElemType e) {
LinkList p = \*L;
int j = 1;
while (p && j<i) {
p = p->next;
++j;
}
// 第 i 个元素不存在
if (!p || j >i) return ERROR;
// 生成一个新节点
LinkList s = (LinkList)malloc(sizeof(Node));
// 插入元素
s->data = e;
s->next = p->next;
p->next = s;
return OK;
}
单链表的删除
/\*\*
移除单链表的第 i 个位置的元素
@param L 单链表,必须存在
@param i 要移除元素的位置标号, 1 ≤ i ≤ ListLength(L)
@param e 要删除的元素返回
@return 操作结果 OK/ERROR
\*/
Status LinkListDelete(LinkList \*L, int i, ElemType \*e) {
LinkList p = \*L;
int j = 1;
while (p && j < i) {
p = p->next;
++j;
}
// 第 i 个节点不存在
if (!p->next || j > i) return ERROR;
LinkList q = p->next;
p->next = q->next;
\*e = q->data;
free(q);
return OK;
}
单链表的整表创建
头插法
/\*\*
随机产生 n 个元素的值, 建立带表头节点的单链线性表 L(头插法)
@param L 创建的线性表 L 返回
@param n 表元素个数
@return 操作结果 OK/ERROR
\*/
Status CreateLinkListHead(LinkList \*L, int n) {
srand((unsigned int)time(0));
\*L = (LinkList)malloc(sizeof(Node));
(\*L) -> next = NULL;
for (int i=0; i<n; i++) {
LinkList p = (LinkList)malloc(sizeof(Node));
p->data = rand() % 100 + 1;
p->next = (\*L)->next;
(\*L)->next = p;
}
return OK;
}
尾插法
/\*\*
随机产生 n 个元素的值, 建立带表头节点的单链线性表 L(尾插法)
@param L 创建的线性表 L 返回
@param n 表元素个数
@return 操作结果 OK/ERROR
\*/
Status CreateLinkListTail(LinkList \*L, int n) {
srand((unsigned int)time(0));
(\*L) = (LinkList)malloc(sizeof(Node));
LinkList r = \*L;
for (int i=0; i<n; i++) {
LinkList p = (Node \*)malloc(sizeof(Node));
p->data = rand()%100+1;
r->next = p;
r = p;
}
r->next = NULL;
return OK;
}
单链表的整表删除
/\*\*
清空单链表
@param L 单链表, 必须存在
@return 操作结果 OK/ERROR
\*/
Status ClearLinkList(LinkList \*L) {
LinkList p = (\*L)->next;
while (p) {
LinkList q = p->next;
free(p);
p=q;
}
(\*L)->next = NULL;
return OK;
}
单链表结构与顺序存储结构优缺点
静态链表
…用数组描述的链表叫静态链表(游标实现法)
静态链表的定义
#define MAXSIZE\_FOR\_STATICLINKLIST 1000
typedef struct {
ElemType data;
int cur;
} Component, StaticLinkList[MAXSIZE_FOR_STATICLINKLIST];
静态链表的初始化
/\*\*
初始化一个静态链表
@param space 静态链表
@return 初始化结果
\*/
Status InitStaticLinkList(StaticLinkList space) {
int i;
for (i=0; i<MAXSIZE_FOR_STATICLINKLIST-1; i++) {
space[i].cur = i+1;
space[i].data = 0;
}
space[MAXSIZE_FOR_STATICLINKLIST-1].cur=0;
return OK;
}
静态链表模拟动态链表的内存分配和释放
/\*\*
模拟动态链表存储空间的分配
@param space 静态链表
@return 若备用空间链表非空, 则返回分配的节点下标, 否则返回0
\*/
int Malloc\_SLL(StaticLinkList space) {
// 数组第0位,始终记录静态链表下一个可用位置
int i = space[0].cur;
if (space[0].cur) {
space[0].cur = space[i].cur;
}
return i;
}
/\*\*
模拟动态l链表存储空间的释放
@param space 静态链表
@param k 要释放的位置
\*/
void Free\_SLL(StaticLinkList space, int k) {
space[k].cur = space[0].cur;
space[0].cur = k;
}
静态链表的插入
/\*\*
静态链表的插入实现
@param space 静态链表
@param i 要插入的位置
@param e 要插入的内容
@return 插入结果
\*/
Status StaticLinkListInsert(StaticLinkList space, int i, ElemType e) {
int j, k, l;
k = MAXSIZE_FOR_STATICLINKLIST-1;
if (i<1 || i>StaticLinkListLength(space)+1) return ERROR;
j = Malloc\_SLL(space);
if (!j) return ERROR;
space[j].data = e;
for (l=1; l<i; l++) {
k = space[k].cur;
}
space[j].cur = space[k].cur;
space[k].cur = j;
return OK;
}
静态链表的删除
/\*\*
静态链表的删除实现
@param space 静态链表
@param i 要删除的元素编号
@return 删除结果
\*/
Status StaticLinkListDelete(StaticLinkList space, int i) {
int j, k;
if (i<1 || i> StaticLinkListLength(space)) return ERROR;
k = MAXSIZE_FOR_STATICLINKLIST-1;
for (j=1; j<i; j++) {
k = space[k].cur;
}
j = space[k].cur; // space[k] 为要删除元素的前一个
space[k].cur = space[j].cur;
Free\_SLL(space, j);
return OK;
}
静态链表的长度计算
/\*\*
获取链表长度
@param space 静态链表
@return 链表长度
\*/
int StaticLinkListLength(StaticLinkList space) {
int j=0;
int i = space[MAXSIZE_FOR_STATICLINKLIST-1].cur;
while (i) {
i = space[i].cur;
j++;
}
return j;
}
静态链表的优缺点
链表代码是最考验逻辑思维能力的。因为,链表代码到处都是指针的操作、边界条件的处理,稍有不慎就容易产生 Bug。链表代码写得好坏,可以看出一个人写代码是否够细心,考虑问题是否全面,思维是否缜密。所以,这也是很多面试官喜欢让人手写链表代码的原因。所以,这一节讲到的东西,你一定要自己写代码实现一下,才有效果。
栈 stack
栈–是限定仅在表尾进行插入和删除操作的线性表
人生,就像一个很大的栈演变。出生时赤条条地来到这个世界,慢慢地长大,渐渐地变老,最终还得赤条条地离开世间。
后进先出 LIFO结构(Last In First Out) 弹夹 网页后退功能
栈顶–允许插入和删除的那一端
重要–进栈出栈变化形式–不是等所有元素进栈后再出栈—可以先进栈的元素直接出栈–后面的元素再进栈出栈
抽象数据类型
ADT 栈(stack)
Data
同线性表,元素具有相同的类型,相邻元素具有前驱和后继关系
Operation
InitStack(\*S):初始化操作,建立一个空栈S
DestroyStack(\*S):若栈存在,则销毁它
ClearStack(\*S):将栈清空
StackEmpty(S):若栈为空,返回true,否则返回false
GetTop(S,\*e):若栈存在且非空,用e返回S的栈顶元素
Push(\*S,e):若栈存在,插入新元素e到栈S中并成为栈顶元素
Pop(\*S,\*e):删除栈S中栈顶元素,并用e返回其值
StackLength(S):返回栈S的元素个数
endADT
顺序栈–栈的顺序存储结构–栈顶不变–栈顶位置变化—游标卡尺
有一种有趣的两栈共享空间,两端为栈底,两个茶杯口对口
链栈–栈的链式存储结构 不需要头结点
typedef struct StackNode{
SElemType data;
struct StackNode \*next;
}StackNode,\*LinkStackPtr;
typedef struct LickStack{
LinkStackPtr top;
int count;
}LinkStack;
//说明在声明变量的时候可以直接StackNode stack1,而不是struct StackNode stack1,不写第一个StackNode也可以
//说明此结构体类的结构体指针为LinkStackPtr
如果栈中的元素变化不可预料,时大时小,用链栈
如果在可控范围内的变化,用顺序栈
迭代–常规for循环
递归–调用自己的函数称为递归函数
菲波那切数列兔子问题–前面相邻两项之和,一年后144只兔子
栈的应用:
1. 逆波兰表示法: 定义后缀表达式,计算四则运算 9+(3-1)*3+10/2 --> 9 3 1-3*+10 2/+
2. 后缀表达式运算规则:
从左到右遍历表达式的每个数字和符号,遇到是数字就进栈,遇到是符号,
就将处于栈顶两个数字出栈,进行运算,运算后的结果再进栈,一直到获得结果
3. 中缀表达式转后缀表达式规则:
从左到右遍历中缀表达式的每个数字和符号,若是数字就输出,即成为后缀表达式的一部分;
若是符号,则判断其与栈顶符号的优先级,是右符号或优先级低于栈顶符号(*/优先+-),
则栈顶元素依次出栈并输出,并将当前符号进栈,一直到最终输出后缀表达式为止
队列 queue
队列–是只允许在一端进行插入操作,而在另一端进行删除操作的线性表
是一种先进先出的线性表(First In First Out)简称FIFO 线程池等有限资源池 阻塞队列和并发队列
允许插入的一端称为队尾,允许删除的一端称为队头
ADT 队列(Queue)
Data
同线性表.元素具有相同的类型,相邻元素具有前驱和后继关系
Operation
InitQueue(\*Q):初始化操作,建立一个空队列Q
DestroyQueue(\*Q):若队列Q存在,则销毁它
ClearQueue(\*Q):将队列Q清空
QueueEmpty(Q):若队列Q为空,返回true,否则返回false
GetHead(Q,\*e):若队列Q存在且非空,用e返回队列Q的队头元素
EnQueue(\*Q,e):若队列Q存在,插入新元素e到队列Q中并成为队尾元素
DeQueue(\*Q,\*e):删除队列Q中队头元素,并用e返回其值
QueueLength(Q)返回队列Q的元素个数
endADT
循环队列的顺序存储结构
typedef int QElemType;
typedef struct{
QElemType data[MAXSIZE];
int front; //头指针
int rear; //尾指针,若队列不空,指向队列尾元素的下一个位置
}SqQueue;
队列的链式存储结构,其实就是线性表的单链表,只不过它只能尾进头出,简称为链队列
串–专门存储字符的字符数组字符序列?
串–是由零个或多个字符组成的有限序列,又名字符串
空格串: 只包含空格的串
子串: 串中任意个数的连续字符组成的子序列
主串: 包含子串的串
ADT 串(string)
Data
串中元素仅由一个字符组成,相邻元素具有前驱和后继关系
Operation
StrAssign(T,\*chars) 生成一个其值等于字符串常量chars的串T
StrCopy(T,S) 串S存在,由串S复制得到串T
ClearString(S) 串S存在,将串清空
StringEmpty(S) 若串S为空,返回true,否则返回false
StrLength(S) 返回串S的元素个数,即串的长度
StrCompare(S,T) 若S>T,返回值大于0,若S=T,返回0,若S<T,返回值<0
Concat(T,S1,S2) 用T返回由S1和S2联接而成的新串
SubString(Sub,S,index,len) 串S存在,1<=index<=StrLength(S),且0<=index<=StrLength(S)-index+1,用Sub返回串S的第index个字符起长度为len的子串
Index(S,T,index) 串S和T存在,T是非空串,1<=index<=StrLength(S),若主串S中存在串T值相同的子串,则返回它在主串S中第index个字符之后第一次出现的位置,否则返回0
Replace(S,T,V) 串S,T,V存在,T是非空串.用V替换主串S中出现的所有与T相等的不重叠的子串
StrInsert(S,index,T) 串S和T存在,1<=index<=StrLength(S)+1,在串S的第index个字符之前插入串T
StrDelete(S,index,len) 串S存在,1<=index<=StrLength(S)-len+1,从串S中删除第index个字符起长度为len的子串
endADT
朴素模式匹配算法: 子串的定位操作 挨个遍历
KMP模式匹配算法:大大避免重复遍历的情况 克努特-莫里斯-普拉特算法 KMP算法
把T串各个位置的j值得变化定义为一个数组next,next的长度就是T串的长度
next[j] = 0 (当j=1时)
= k ("P1...P(k-1)" = "P(j-k+1)...P(j-1)")
= 1 (其他情况)
j 123456
T abcdex
next[j] 011111
j 123456
T abcabx
next[j] 011123
j 123456789
T ababaaaba
next[j] 011234223
优化KMP模式匹配算法:
在计算next值时,判断T[next]的值与当前位置T[j]的值是否相等,如果相等,
nextval[j]的值就等于nextval[next]的值
j 123456789
T ababaaaba
next[j] 011234223
nextval[j] 010104210
4. 非线性表
线性表
非线性表,比如二叉树、堆、图等。之所以叫非线性,是因为,在非线性表中,数据之间并不是简单的前后关系。
树 tree
树–是n个节点的有限集.n=0时为空树
在任意一棵非空树中:
1>有且仅有一个特定的称为根(root)的结点
2>当n>1时,其余结点可分为m(m>0)个互不相交的有限集T1,T2...Tm,
其中每一个集合本身又是一棵树,并且称为根的子树
结点拥有的子树数称为结点的度,
度为0的结点称为 叶结点 或 终端结点,
度不为0的称为非终端结点或分支结点,
除根结点外,也称为内部结点
结点的层次从根开始定义起,
根为第一层,
树中结点的最大层次称为树的深度或高度
如果将树中结点的各子树看成从左至右有次序的,
不能互换的,
则称该树为有序树
“高度”这个概念,其实就是从下往上度量,比如我们要度量第 10 层楼的高度、第 13 层楼的高度,起点都是地面。所以,树这种数据结构的高度也是一样,从最底层开始计数,并且计数的起点是 0。
“深度”这个概念在生活中是从上往下度量的,比如水中鱼的深度,是从水平面开始度量的。所以,树这种数据结构的深度也是类似的,从根结点开始度量,并且计数起点也是 0。
“层数”跟深度的计算类似,不过,计数起点是 1,也就是说根节点的位于第 1 层。
抽象数据类型
ADT 树(tree)
Data
树由一个根结点和若干棵子树构成,树中结点具有相同数据类型及层次关系
Operation
InitTree(\*T) 构造空树T
DestroyTree(\*T) 销毁树T
CreateTree(\*T,definition) 按definition中给出的树的定义来构造树
ClearTree(\*T) 若树T存在,则将树T清为空树
TreeDepth(T) 返回T的深度
Root(T) 返回T的根结点
Value(T,cur_e) cur_e是树T中一个结点,返回此结点的值
Assign(T,cur_e,value) 给树T中结点cur_e赋值为value
Parent(T,cur_e) 若cur_e是树T的非根结点,则返回它的双亲,否则返回空
LeftChild(T,cur_e) 若cur_e是树T的非根结点,则返回它的最左孩子,否则返回空
RightSibling(T,cur_e) 若cur_e有右兄弟,则返回它的右兄弟,否则返回空
InsertChild(\*T,\*p,i,c) 其中p指向树T的某个结点,i为所指结点p的度加上1,
非空树c与T不相交,操作结果为插入c为树T中p指结点的第i棵子树
DeleteChild(\*T,\*p,i) 其中p指向树T的某个结点,i为所指结点p的度,
操作结果为删除T中所指节点的第i棵子树
endADT
树的存储结构:
1 双亲表示法:
#define MAX\_TREE\_SIZE 100
typedef int TElemType; //树结点的数据类型
typedef struct PTNode{ //结点结构
TElemType data; //结点数据
int parent; //双亲位置
}PTNode;
typedef struct{ //树结构
PTNode nodes[MAX_TREE_SIZE]; //结点数组
int r,n; //根的位置和结点数
}PTree;
可以扩展域,增加双亲域,长子域,兄弟域
2 孩子表示法:
多重链表,每个结点有多个指针域,每个指针指向一棵子树的根节点
2.1 每个结点指针域的个数等于树的度 牺牲空间
2.2 每个结点指针域的个数等于该结点的度 牺牲时间
2.3 每个结点的孩子结点排列起来,以单链表作存储结构,
则n个结点有n个孩子链表,然后n个头指针又组成一个线性表,
顺序存储结构,存放进一个一维数组中
#define MAX\_TREE\_SIZE 100
typedef struct CTNode{ //孩子结点
int child;
struct CTNode \*next;
}\*ChildPtr;
typedef struct{ //表头结构
TElemType data;
ChildPtr firstchild;
}CTBox;
typedef struct{ //树结构
CTBox nodes[MAX_TREE_SIZE]; //结点数组
int r,n; //根的位置和结点数
}CTree;
3.孩子兄弟表示法
typedef struct CSNode{
TElemType data;
struct CSNode \*firstChild,\*rightSib;
}CSNode,\*CSTree;
二叉树–
是n(n>=0)个结点的有限集合,
该集合或者为空集(称为空二叉树),
或者由一个根结点和两棵互不相交的,
分别称为根结点的左子树和右子树的二叉树组成
每个结点最多有两棵子树,左子树和右子树是有顺序的,
即使只有一棵子树,也要区分左还是右,且次序不能颠倒
线性表结构可以理解为是树的一种极其特殊的表现形式
左斜树,右斜树,满二叉树,完全二叉树(按层序顺序编号)
二叉树性质:
1.在二叉树的第i层上至多有2^(i-1)个结点
2.深度为k的二叉树至多有2^k -1个结点
3.对任何一棵二叉树T,如果其终端结点数为n0,度为2的结点数为n2,则n0 = n2 + 1
4.具有n个结点的完全二叉树的深度为[log2^n]+1
5.寻找双亲为i/2等
二叉树的顺序存储结构
适用于完全二叉树,按顺序存入数组中 ABCDEFGHI
二叉链表
二叉树每个结点最多有两个孩子,即一个数据域和两个指针域
typedef struct BiTNode{ //结点结构
TElemType data; //结点数据
struct BiTNode \*lChild,\*rChild; //左右孩子指针
}BiTNode,\*BiTree;
二叉树的遍历,是指从根结点出发,
按照某种次序依次访问二叉树中所有结点,
使得每个结点被访问一次且仅被访问一次
1>前序遍历
先访问根节点,然后前序遍历左子树,再前序遍历右子树 ABDGHCEIF
void PreOrderTraverse(BiTree T){
if(T==NULL){
return;
}
printf("%c",T->data);
PreOrderTraverse(T->lCHild);//先遍历左子树
PreOrderTraverse(T->rChild);//然后遍历右子树
}
2>中序遍历
从根结点开始,中序遍历根结点的左子树,然后访问根结点,最后中序遍历右子树 GDHBAEICF
void PreOrderTraverse(BiTree T){
if(T==NULL){
return;
}
PreOrderTraverse(T->lCHild);//先遍历左子树
printf("%c",T->data);
PreOrderTraverse(T->rChild);//然后遍历右子树
}
3>后序遍历
从左到右先叶子后结点遍历左右子树,最后访问根结点 GHDBIEFCA
void PreOrderTraverse(BiTree T){
if(T==NULL){
return;
}
PreOrderTraverse(T->lCHild);//先遍历左子树
PreOrderTraverse(T->rChild);//然后遍历右子树
printf("%c",T->data);
}
4>层序遍历
从根结点开始访问,从上而下逐层遍历,在同一层中,从左到右对结点逐个访问 ABCDEFGHI
建立二叉树:
将二叉树中每个结点的空指针引出一个虚结点,其值为"#",称为扩展二叉树 AB#D##C##
线索二叉树:
把空余的指针域指向前驱后继元素,指向前驱和后继的指针称为线索,
加上线索的二叉链表称为线索链表,相应的二叉树就称为线索二叉树
并且除了两个左右指针域,再增加两个标识BOOL值,lTag与rTag,lTag为0时指向左孩子,
为1时指向前驱,rTag为0时指向右孩子,为1时指向后继
typedef enum(Link,Thread) PointerTag; //Link==0表示指向左右孩子指针 Thread==1表示指向前驱或后继的线索
typedef struct BiTNode{ //结点结构
TElemType data; //结点数据
struct BiTNode \*lChild,\*rChild; //左右孩子指针
PointerTag lTag; //左标识
PointerTag rTag; //右标识
}BiTNode,\*BiTree;
树转换为二叉树:
1.加线.在所有兄弟结点之间加一条连线
2.去线.每个结点只保留与第一个孩子结点的连线,删除它与其他孩子结点之间的连线
3.第一个孩子是二叉树结点的左孩子,兄弟转换过来的孩子是结点的右孩子
森林转换为二叉树:
1.把每个树转换为二叉树
2.把后一棵二叉树的根结点作为前一棵二叉树的根结点的右孩子
二叉树转换为树:
1.加线.将子级与孙子级的右孩子结点都作为此结点的孩子连接
2.去线.删除所有结点与其右孩子结点的连线
二叉树转换为森林:
1.把所有右孩子分离出来
2.把分离出来的二叉树转换为树
森林的前序遍历和二叉树的前序遍历结果相同,森林的后序遍历和二叉树的中序遍历结果相同
赫夫曼树
是压缩文件时用的最基本的压缩编码方法
树中一个结点到另一个结点之间的分支构成两个结点之间的路径,路径上的分支数目称为路径长度
结点的带权路径长度为从该结点到树根之间的路径长度与结点上权(值比例)的乘积
带权路径长度WPL最小的二叉树称作赫夫曼树.最优二叉树.
构造方法:
1.把各结点按照权值从小到大排列
2.取最小权值的两个结点作为新结点N1的两个子结点,新结点N1的权值为两个叶子权值得和
3.将N1替换两个结点重新排列
4.重复以上三步
传递时,构造好赫夫曼树,双方约定好赫夫曼编码规则,按照路径寻找数据
一般规定赫夫曼树的左分支代表0,右分支代表1,从根结点到叶子结点经过的路径分支组成的0和1的序列便为该结点对应字符的编码,这就是赫夫曼编码
图 社交网络 如何存储微博、微信等这些社交网络的好友关系?
是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为:G(V,E),
其中,G表示一个图,V是图G中顶点的集合,E是图G中边集合
顶点(元素)之间的边没有方向,则称这条边为无向边(),全是无向边的图称为无向图
G(V,{E}) V={A,B,C,D} E={(A,D),(B,A),(C,A),(B,C)}
有方向则为有向边<>,也称为弧,弧头,弧尾,有向图
G(V,{E}) V={A,B,C,D} E={<A,D>,<B,A>,<C,A>,<B,C>}
简单图:无重复边与指向自身的边
稀疏图,稠密图,连通图,子图,
权:与图的边或弧相关的数
网:带权的图
无向完全图:任意两个顶点之间都存在边 n(n-1)/2条边
有向完全图:任意两个顶点之间都存在方向互为相反的两条弧 n(n-1)条边
无向图的度,有向图的入度,出度
ADT 图
Data
顶点的有穷非空集合和边的集合
Operation
CreateGraph(\*G,V,VR) 按照顶点集V和边弧VR的定义构造图G
DestroyGraph(\*G) 图G存在则销毁
LocateVex(G,u) 若图G中存在顶点u,则返回图中的位置
GetVex(G,v) 返回图G中顶点v的值
PutVex(G,v,value) 将图G中顶点v赋值value
FirstAdjVex(G,\*v) 返回顶点v的一个邻接顶点,若顶点在G中无邻接顶点返回空
NextAdjVex(G,v,\*w) 返回顶点v相对于顶点w的下一个邻接顶点,若w是v的最后一个邻接点则返回空
InsertVex(\*G,v) 在图G中增添新顶点v
DeleteVex(\*G,v) 删除图G中顶点v及其相关的弧
InsertArc(\*G,v,w) 在图G中增添弧<v,w>,若G是无向图,还需添加对称弧<w,v>
DeleteArc(\*G,v,w) 在图G中删除弧<v,w>,若G是无向图,还需删除对称弧<w,v>
DFSTraverse(G) 对图G中进行深度优先遍历,在遍历过程对每个顶点调用
HFSTraverse(G) 对图G中进行广度优先遍历,在遍历过程对每个顶点调用
endADT
存储方式
1.邻接矩阵
用两个数组,一个一维数组存储图中顶点信息,一个二维数组(邻接矩阵)存储图中的边或弧的信息
无向图的边数组是一个对称矩阵
2.邻接表—链表数组–
针对有向图,用数组与链表结合,一个一维数组存储图中顶点信息,每个顶点的所有邻接点构成一个线性表(入度)
逆邻接表(出度)
3.十字链表
把邻接表和逆邻接表整合在了一起
4.邻接多重表
针对无向图,根据十字链表设计
5.边集数组
由两个一维数组构成,一个存储顶点的信息,另一个存储边的信息,这个边数组每个数据元素包括起点下标,终点下标,权
最短路径
深度和广度优先搜索:如何找出社交网络中的三度好友关系?
还有 A*、D*、IDA* 等启发式图上搜索算法。
// java
public class Graph { // 无向图
private int v; // 顶点的个数
private LinkedList<Integer> adj[]; // 邻接表 链表数组
public Graph(int v) {
this.v = v;
adj = new LinkedList[v]; // 链表数组
for (int i=0; i<v; ++i) {
adj[i] = new LinkedList<>();//单个链表
}
}
public void addEdge(int s, int t) { // 无向图一条边存两次
adj[s].add(t);
adj[t].add(s);
}
}
1.深度优先遍历 DFS
广度优先搜索(Breadth-First-Search),我们平常都把简称为 BFS。直观地讲,它其实就是一种“地毯式”层层推进的搜索策略,即先查找离起始顶点最近的,然后是次近的,依次往外搜索。
// 其中 s 表示起始顶点,t 表示终止顶点。我们搜索一条从 s 到 t 的路径。
// 实际上,这样求得的路径就是从 s 到 t 的最短路径。
public void bfs(int s, int t) {
if (s == t) return;
boolean[] visited = new boolean[v];
visited[s]=true;
Queue<Integer> queue = new LinkedList<>();
queue.add(s);
int[] prev = new int[v];
for (int i = 0; i < v; ++i) {
prev[i] = -1;
}
while (queue.size() != 0) {
int w = queue.poll();
for (int i = 0; i < adj[w].size(); ++i) {
int q = adj[w].get(i);
if (!visited[q]) {
prev[q] = w;
if (q == t) {
print(prev, s, t);
return;
}
visited[q] = true;


**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**
**[需要这份系统化资料的朋友,可以戳这里获取](https://bbs.youkuaiyun.com/topics/618545628)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**
/ 链表数组
for (int i=0; i<v; ++i) {
adj[i] = new LinkedList<>();//单个链表
}
}
public void addEdge(int s, int t) { // 无向图一条边存两次
adj[s].add(t);
adj[t].add(s);
}
}
1.深度优先遍历 DFS
广度优先搜索(Breadth-First-Search),我们平常都把简称为 BFS。直观地讲,它其实就是一种“地毯式”层层推进的搜索策略,即先查找离起始顶点最近的,然后是次近的,依次往外搜索。
[外链图片转存中…(img-taGVhS9M-1714802394075)]
// 其中 s 表示起始顶点,t 表示终止顶点。我们搜索一条从 s 到 t 的路径。
// 实际上,这样求得的路径就是从 s 到 t 的最短路径。
public void bfs(int s, int t) {
if (s == t) return;
boolean[] visited = new boolean[v];
visited[s]=true;
Queue<Integer> queue = new LinkedList<>();
queue.add(s);
int[] prev = new int[v];
for (int i = 0; i < v; ++i) {
prev[i] = -1;
}
while (queue.size() != 0) {
int w = queue.poll();
for (int i = 0; i < adj[w].size(); ++i) {
int q = adj[w].get(i);
if (!visited[q]) {
prev[q] = w;
if (q == t) {
print(prev, s, t);
return;
}
visited[q] = true;
[外链图片转存中...(img-nHrPvzCt-1714802394075)]
[外链图片转存中...(img-FYOSuNVu-1714802394075)]
**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**
**[需要这份系统化资料的朋友,可以戳这里获取](https://bbs.youkuaiyun.com/topics/618545628)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**