赞
踩
前提说明:
1、笔记基于 数据结构与算法基础(青岛大学-王卓)_哔哩哔哩_bilibili 整理,老师讲得很通彻,可观看视频学习后,若有遗忘,将本笔记当手册使用。
2、编程语言使用C/C++语言,存在混用情况,部分为伪代码,可能存在直接粘贴代码报错的情况,但不影响理解数据结构本身。数据结构看别人的代码主要是为了理解算法的思想,编程要自己动手实践才能把知识真正变成自己的东西。ヾ(◍°∇°◍)ノ゙
3、本笔记内容尚未完善,后面有时间的时候会不断修改更新,若发现问题,希望大家多多指正Thanks♪(・ω・)ノ
(随机存取)
#define LIST_INIT_SIZE 100 //线性表存储空间的初始分配量
typedef struct{
ElemType elem[LIST_INIT_SIZE]; //存放的元素(其中ElemType是数据类型,根据需要选择int等)
int length; //当前长度
}SqList;
typedef struct{
ElemType data[MaxSize];
int length;
}SqList;
typedef struct{
ElemType *data;
int length;
}SqList;
SqList L;
L.data = (ElemType*)malloc(sizeof(ElemType)*MaxSize);
//其中 (ElemType*) 的作用是将开辟的空间 强制转换 为 对应数据类型 的 指针
/*需要加载头文件:<stdlib.h>
malloc(m)函数:开辟m个字节长度的地址空间,并返回这段空间的首地址
sizeof(x)运算,计算变量x的长度
free(p)函数,释放指针p所指变量的存储空间,即彻底删除一个变量
*/
InitList(&L); //初始化操作,建立一个空的线性表L
DestroyList(&L); //销毁已存在的线性表L
ClearList(&L); //将线性表L清空
ListInsert(&L,i,elem); //在线性表L中第i个位置插入新元素elem
ListDelte(&L,i,&e); //删除线性表L中第i个位置元素,用e返回
IsEmpty(L); //若线性表为空,返回true,否则返回false
ListLength(L); //返回线性表L的元素个数
LocateElem(L,e); //L中查找与给定值e相等的元素,若成功返回该元素在表中的序号,否则返回0
GetElem(L,i,&e); //将线性表L中的第i个元素返回给e
PS:
//函数结果状态代码
#define TRUE 1
#define False 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1
#define OVERFLOW -2
//Status 是函数的类型,其值时函数结果状态代码
typedef int Status;
typedef char ElemType;
线性表的初始化(参数用引用)
Status InitList_Sq(SqList &L){ //构造一个空的顺序表L
L.elem = new ElemType[MAXSIZE]; //为顺序表分配空间
if(!L.elem){
exit(OVERFLOW); // 存储分配失败
}else{
L.length = 0; //空表长度为0
return OK;
}
}
销毁线性表
void DestroyList(SqList &L){
if(L.elem){
free(L.elem); //释放存储空间
}
}
清空线性表
void ClearList(SqList &L){
L.length = 0; //将线性表的长度置为0(逻辑清空)
}
求线性表长度
int GetLength(SqList L){
return (L.length);
}
判断线性表是否为空
int IsEmpty(SqList L){
if(L.length == 0){
return 1;
}else{
return 0;
}
}
顺序表的取值(根据位置 i 获取相应位置元素的内容)
int GetElem(SqList L, int i, ElemType &e){
if(i < 1 || i > L.length){ //判断i值合不合理
return ERROR;
}else{
e = L.elem[i-1]; //第i-1的单元存储着第i个元素
return OK;
}
}
顺序表的查找(从表的一端开始,逐个进行记录的关键字和给定值的比较)
int LocateElem(SqList L, ElemType e){
int i;
for(i = 0; i < L.length; i++){ //逐个遍历
if(L.elem[i] == e){
return i+1; //查找成功返回序号
}
}
return 0; //查找失败返回0
}
顺序表的插入(整体考虑插入位置在 首,尾,中间 的情况)
Status ListInsert_Sq(SqList &L, int i, ElemType e){
if(i < 1 || i > L.length + 1){ //i值不合法
return ERROR;
}
if(L.length == MAXSIZE){ //当前存储空间已满
return ERROR;
}
int j;
for(j = L.length - 1; j >= i-1; j--){ //插入位置及之后的元素后移
L.elem[j+1] = L.elem[j];
}
L.elem[i-1] = e; //插入
L.length++; //表长增1
return OK;
}
顺序表的删除(整体考虑删除位置在 首,尾,中间 的情况)
Status ListDelete_Sq(SqList &L, int i){
if(i < 1 || i > L.length + 1){ //i值不合法
return ERROR;
}
int j;
for(j = i; j <= L.length - 1; j++){
L.elem[j-1] = L.elem[j]; //被删除元素之后的元素前移
}
L.length--; //表长减1
return OK;
}
顺序表小结:
(顺序存取)
单链表、双链表、循环链表:
讨论1:如何表示空表?
无头结点:头指针为空时表示空表
有头结点:头结点的指针域为空时表示空表
讨论2:链表设置头结点有什么好处?
1、便于首元结点的处理
首元结点的地址保存在头结点的指针域中,所以在链表的第一个位置上的操作和其他位置一致,无需特殊处理。
2、便于空表和非空表的统一处理
无论链表是否为空,头指针都是指向头结点的非空指针,因此,空表和非空表的处理也就统一了。
讨论3:头结点的数据域内装的是什么?
头结点的数据域可以为空,也可以存放线性表长度等附加信息,但此结点不能计入链表长度值。
链表(链式存储结构)的特点
typedef struct Lnode{ //声明结点的类型和指向结点的指针类型
ElemType data; //结点的数据域
struct Lnode *next; //结点的指针域
}LNode, *LinkList; //LinkList为指向结构体Lnode的指针类型
/*
定义链表: LinkList L; (或:LNode *L)
定义结点指针p: LNode *p; (或:LinkList p)
*/
例:存储学生学号、姓名、成绩的单链表结点类型定义
typedef struct student{ char num[8]; //数据域 char name[8]; //数据域 int score; //数据域 struct student *next; //指针域 } -------------------------------------------------------------------------- 为了统一链表操作,通常这样定义: typedef struct{ char num[8]; char name[8]; int score; }ElemType; typedef struct Lnode{ ElemType data; //数据域 struct Lnode *next; //指针域 }LNode, *LinkList;
单链表的初始化
Status InitList_L(LinkList &L){
L = new LNode; // L = (LinkList)malloc (sizeof(LNode));
L -> next = NULL; //头结点指针域置空
}
判断链表是否为空
int ListEmpty(LinkList L){
if(L->next != NULL){ //非空
return 0;
}else{
return 1;
}
}
单链表的销毁(清除所有结点(包括头结点))
Status DestroyList_L(LinkList &L){
LNode *p; //或LinkList p;
while(L != NULL){
p = L;
L = L->next;
delete p; // free(p);
}
}
清空链表(清除除头结点外的所有结点)
Status ClearList(LinkList &L){
LNode *p,*q;
p = L->next;
while(p != NULL){
q = p->next;
delete p;
p = q;
}
L->next = NULL; //头结点指针域置为空
return OK;
}
单链表的表长
int ListLength(LinkList L){
LNode *p;
p = L->next;
int i = 0;
while(p != NULL){
i++;
p = p->next;
}
return i;
}
单链表的取值(获取第 i 个数据元素的内容)
Status GetElem_L(LinkList L, int i, ElemType &e){ LNode *p; p = L->next; int j = 1; while(p != NULL && j < i){ p = p->next; ++j; } if(p == NULL || j > i){ return ERROR; //第i个元素不存在 }else{ e = p->data; return OK; } }
根据指定数据查找所在位置(地址)
Lnode *LocateElem_L(LinkList L, ElemType e){
LNode *p = L->next;
while(p != NULL && p->data != e){
p = p->next;
}
return p;
}
根据指定数据查找所在位置序号
int LocateElem_L(LinkList L, ElemType e){
LNode *p = L->Next;
int j = 1;
while(p != NULL && p->data != e){
p = p->next;
j++;
}
if(p != NULL){
return j;
}else{
return 0;
}
}
在第i个结点前插入新结点
Status ListInsert_L(LinkList &L, int i, ElemType e){ LNode *p = L; int j = 0; while(p != NULL && j < i-1){ //寻找第i-1个结点 p = p->next; j++; } if(p == NULL || j > i-1){ return ERROR; }else{ LNode *s = new LNode; s->data = e; s->next = p->next; p-next = s; return OK; } }
删除第i个数据元素
Status ListDelete_L(LinkList &L, int i, ElemType &e){ LNode *p = L; int j = 0; while(p->next != NULL && j < i-1){ p = p->next; j++; } if(p->next == NULL || j > i-1){ return ERROR; }else{ LNode *q = p->next; p->next = q->next; e = q->data; //保存删除结点的数据域 delete q; //free(q); return OK; } }
头插法建立单链表
void CreateList_H(LinkList &L, int n){
L = new LNode; // L = (LinkList)malloc (sizeof(LNode));
L->next = NULL; //先建立一个带头结点的单链表
int i;
for(i = n; i > 0; i--){
LNode *p = new LNode; //p = (LNode*)malloc(sizeof(LNode));
cin >> p->data; //scanf(&p->data);
p->next = L->next;
L->next = p;
}
}
尾插法建立单链表
void CreateList_R(LinkList &L, int n){
L = new LNode; // L = (LinkList)malloc (sizeof(LNode));
L->next = NULL;
LNode*p,*r;
r = L;
int i;
for(i = 0; i < n; i++){
p = new LNode;
cin >> p->data;
p->next = NULL;
r->next = p;
r = p; //r指向新的尾结点
}
}
存储方式:
同一般线性表的顺序存储结构相同,
利用一组地址连续的存储单元依次存放自栈底到栈顶的数据元素。栈底一般在低地址端。
栈的顺序存储结构:顺序栈
栈的链式存储结构:链栈
####顺序栈的表示
#define MAXSIZE 100
typedef struct{
SElemType *base; //栈底指针
SElemType *top; //栈顶指针
int stacksize; //栈可用最大容量
}SqStack;
顺序栈的初始化
Status InitStack(SqStack &S){ //构造一个空栈
S.base = new SElemType[MAXSIZE];
if(!S.base){
exit(OVERFLOW); //存储分配失败
}else{
S.top = S.base; //栈顶指针等于栈底指针
S.stacksize = MAXSIZE;
return OK;
}
}
判断栈是否为空
Status StackEmpty(SqStack S){
if(S.top == S.base){ //空栈
return TRUE;
}else{
return FALSE;
}
}
求顺序栈的长度
int StackLength(SqStack S){
return S.top - S.base;
}
清空顺序栈
Status ClearStack(SqStack S){
if(S.base){ //栈存在
S.top = S.base;
}
return OK;
}
销毁顺序栈
Status DestroyStack(SqStack &S){
if(S.base){ //栈存在
delete S.base;
S.stacksize = 0;
S.base = S.top = NULL;
}
return OK;
}
顺序栈的入栈
Status Push(SqStack &S, SElemType e){
if(S.top - S.base == S.stacksize){ //栈满,无法入栈
return ERROR;
}else{
*S.top = e;
S.top++;
return OK;
}
}
顺序栈的出栈
Status Pop(SqStack &S, SElemType e){
if(S.top == S.base){ //栈为空栈
return ERROR;
}else{
e = *S.top;
S.top--;
return OK;
}
}
链栈是运算受限的单链表,只能在链表头部进行操作
typedef struct StackNode{
SElemType data;
struct StackNode *next;
}StackNode, *LinkStack;
LinkStack S;
链栈的入栈
Status Push(LinkStack &S, SElemType e){
stackNode *p;
p = new StackNode; //生成新结点p
p->data = e;
p->next = S; //将新结点插入栈顶
S = p; //修改栈顶指针
return OK;
}
链栈的出栈
Status Pop(LinkStack &S, SElemType &e){
if(S == NULL){
return ERROR;
}else{
e = S->data;
stackNode *p;
p = S;
S = S->next;
delete p;
return OK;
}
}
取栈顶元素
SElemType GetTop(LinkStack S){
if(S != NULL){
return S->data;
}
}
void p(参数表){
if(递归结束条件)可直接求解步骤; //-----基本项
else p(较小的参数); //-----归纳项
}
(遵循后调用,先返回----->栈)
调用前,系统完成:
调用后,系统完成:
#define MAXQSIZE 100 //最大队列长度
Typedef struct{
QElemType *base; //初始化的动态分配存储空间
int front; //数组的头下标
int rear; //数组的尾下标
}SqQueue;
问题:rear == MAXQSIZE且front != 0(第一个位置下标为0)时,再入队会发生“溢出”,但base[0]~base[front-1]的存储位置是空的(之前的元素已出队),这被称为“假溢出”。
解决假溢出的方法
将队列中的元素依次向队头方向移动。
缺点:浪费时间。每移动一次,队列中的元素都要移动。
将队空间设想成一个循环的表,即分配给队列的m个存储单元可以循环使用,当rear为MAXQSIZE时,若向量的开始端为空,又可以从头使用空着的空间。当front为MAXQSIZE时也一样。——引入循环队列
循环队列
核心思路:base[0]接在base[MAXQSIZE-1]之后,若rear+1==M,则令rear=0;
实现方法:利用mod(%)运算;
每次插入元素:Q.base[Q.rear]=element; Q.rear = (Q.rear+1)%MAXQSIZE;
每次删除元素:element = Q.base[s.front]; Q.front = (Q.front+1)%MAXQSIZE;
队空:front == rear; 队满:front == rear; -------->? 无法判断队空还是队满
解决方案:
循环队列初始化
Status InitQueue(SqQueue &Q){
Q.base = new QElemType[MAXQSIZE]; //分配数组空间
if(!Q.base){
exit(OVERFLOW); //存储分配失败
}else{
Q.front = Q.rear = 0; //头指针尾指针置为0,队列为空
return OK;
}
}
求循环队列长度
int QueueLength(SqQueue Q){
return (Q.rear - Q.front + MAXQSIZE) % MAXQSIZE;
}
循环队列的入队
Status EnQueue(SqQueue &Q, QElemType &e){
if((Q.rear + 1) % MAXQSIZE == Q.front){ //此法为空出一个空间判断队空队满
return ERROR; //队已满
}else{
Q.base[Q.rear] = e; //新元素加入队尾
Q.rear = (Q.rear + 1) % MAXQSIZE; //队尾指针+1
return OK;
}
}
循环队列的出队
Status DeQueue(SqQueue &Q, QElemType &e){
if(Q.rear == Q.front){
return ERROR; //队空
}else{
e = Q.base[front];
Q.front = (Q.front + 1) % MAXQSIZE; //队头指针+1
return OK;
}
}
循环队列取队头元素
SElemType GetHead(SqQueue Q){
if(Q.front == Q.rear){ //队列为空
exit(ERROR);
}else{
return Q.base[front];
}
}
若用户无法估计所用队列的长度,宜采用链队列
链队列的类型定义
#define MAXQSIZE 100 //队列的最大长度
typedef struct Qnode{
QElemType data;
struct Qnode *next;
}QNode, *QueuePtr;
typedef struct{
QueuePtr front; //队头指针
QueuePtr rear; //队尾指针
}LinkQueue;
链队列的初始化
Status InitQueue(LinkQueue &Q){
Q.front = Q.rear = new QNode;
if(!Q.front){
exit(OVERFLOW);
}else{
Q.front->next = NULL;
return OK;
}
}
链队列的销毁
Status DestroyQueue(LinkQueue &Q){
//算法思想:从队头开始,依次释放所有结点
QNdoe *p = NULL; //或者不新建指针,直接用尾指针
while(Q.front){
p = Q.front->next; //Q.rear = Q.front->next;
delete Q.front;
Q.front = p; //Q.front = Q.rear;
}
return OK;
}
链队列的入队(将元素e入队)
Status EnQueue(LinkQueue &Q, QElemType e){
QNode *p = new QNode;
if(!p){
exit(OVERFLOW); //分配空间失败
}else{
p->data = e;
p->next = NULL;
Q.rear->next = p;
Q.rear = p;
return OK;
}
}
链队列的出队
Status DeQueue(LinkQueue &Q, QElemType &e){ QNode *p = new QNode; if(Q.front == Q.rear){ //队列为空 return ERROR; }else{ //PS:Q.front->next才是存放第一个元素的结点,Q.front不是 p = Q.front->next; e = p->data; Q.front->next = p->next; if(Q.rear == p){ //此步的目的是,若出队的结点已是尾结点,则也需要修改尾指针Q.rear,使其与头结点指向相同位置 Q.rear = Q.front; } delete p; return OK; } }
求链队列的队头元素
Status GetHead(LinkQueue Q, QElemType e){
if(Q.front == Q.rear){ //队列为空
return ERROR;
}else{
e = Q.front->next->data;
return OK;
}
}
串:内容受限的线性表
数组、广义表:线性结构的推广
#define MAXLEN 255
typedef struct{
char ch[MAXLEN + 1]; //存储串的一维数组
int length; //串的当前长度
}SString
#define CHUNKSIZE 80 //块的大小可由用户定义
typedef struct Chunk{
char ch[CHUNKSIZE];
struct Chunk *next;
}Chunk; //结点的结构
typedef struct{
Chunk *head, *tail; //串的头指针和尾指针
int curlen; //串的当前长度
}LString; //字符串的块链结构
模式匹配算法
算法目的:确定主串中所含子串(模式串)第一次出现的位置(定位)
算法应用:搜索检查、拼写检查、语言翻译、数据压缩
算法种类:
BF算法(简单匹配算法)-----穷举法思路
时间复杂度:O((n-m+1)m),,若m<<n,则为O(nm)
int Index_BF(SString S, SStirng T){ //S为主串,T为模式串 int i = 1, j = 1; while(i <= S.length && j <= T.length){ if(S.ch[i] == T.ch[j]){ ++i; ++j; }else{ i = i - (j - 1) + 1; //i回溯到开始位置的下一处 j = 1; //j回到模式串开头 } } if(j > T.length){ //此时表明匹配成功 return i - (T.length - 1); //返回匹配成功的第一个字符在主串中的位置 }else{ return 0; //匹配不成功 } }
KMP算法
n(n>=0)个结点的有限集。
n=0,称为空树;
n>0,则它满足如下两个条件:
(1)有且仅有一个特定的称为根(root)的结点;
(2)其余结点可分为m(m>=0)个互不相交的有限集T1,T2,T3…Tm,其中每一个 集合本身又是一棵树,并称为根的子树(Sub Tree)。
树的基本术语:
根节点:非空树中无前驱点的结点
结点的度:结点拥有的子树数
分支结点:非终端结点
内部结点:根结点以外的分支结点
叶子:终端结点
树的度:树内各结点的度的最大值
双亲、孩子:结点的子树的根称为该结点的孩子,该结点称为孩子的双亲
兄弟:双亲为同一结点的结点
堂兄弟:双亲在同一层的结点
结点的祖先:从根到该结点所经分支上的所有结点
树的深度:树中结点的最大层次
有序树:树中结点的各子树从左至右有次序(最左边的为第一个孩子)
无序树:树中结点的各子树无次序
森林:m(m>0)课互不相交的树的集合
线性结构 树结构
第一个元素:无前驱 根结点(只有一个):无双亲
最后一个元素:无后继 叶子结点(可以有多个):无孩子
其他数据元素:一个前驱,一个后继 中间结点:一个双亲,多个孩子
n(n>=0)个结点的有限集,它或者是空集(n=0),或者由一个根结点及两棵互不相交的分别称作这个根的左子树和右子树的二叉树组成。
特点:
注:二叉树不一定是树(概念不同)
在二叉树的第i层上至多有2i-1个结点(i>=1),至少1个结点
深度为k的二叉树至多有2k-1个结点(k>=1),至少k个结点
对任何一棵二叉树T,如果其叶子数为n0,度为2的结点数为n2,则n0=n2+1
特殊形式的二叉树(满二叉树一定是完全二叉树)
具有n个结点的完全二叉树的深度为[log2n+1]+1。(PS:[n]表示不大于n的最大整数)
完全二叉树任一结点i,其双亲结点为[i/2],左孩子结点为[2i],右孩子结点为[2i+1]
实现:按满二叉树的结点层次编号,依次存放二叉树(不管是否是完全二叉树)中的数据元素
//二叉树的顺序存储表示
#define MAXTSIZE 100
Typedef TElemType SqBiTree[MAXTSIZE]
SqBiTree bt;
缺点:最坏情况->深度为k且只有k个结点的树需要长度为2k-1的一维数组
特点:结点间关系蕴含在其存储位置中;浪费空间,仅适用于存完全二叉树
//二叉树的链式存储结构表示(二叉链表)
typedef struct BiNode{
TElemType data;
struct BiNode *lchild,*rchild;
}BiNode,*BiTree;
//PS:若建立三叉链表,则再增添一个指向双亲的指针
特点:在n个结点的二叉链表中,有n+1个空指针域
已知二叉树的先序、中序或中序、后序可以推出二叉树的构造
####递归算法
//先序遍历
void PreOrderTraverse(BiTree T){
if(T == NULL){
exit(0); //空二叉树
}else{
visit(T.data); //访问根结点
PreOrderTraverse(T->lchild); //递归遍历左子树
PreOrderTraverse(T->rchild); //递归遍历右子树
}
}
//中序遍历
void InOrderTraverse(BiTree T){
if(T == NULL){
exit(0);
}else{
InOrderTraverse(T->lchild); //递归遍历左子树
visit(T.data); //访问根结点
InOrderTraverse(T->rchild); //递归遍历右子树
}
}
//后序遍历
void PostOrderTraverse(BiTree T){
if(T == NULL){
PostOrderTraverse(T->lchild); //递归遍历左子树
PostOrderTraverse(T->rchild); //递归遍历右子树
visit(T.data); //访问根结点
}
}
时间复杂度:O(n) //每个结点只访问一次
空间复杂度:O(n) //栈占用的最大辅助空间
//中序遍历 void InOrderTraverse(BiTree T){ BiTree p = T; stack st; while(p != NULL || !st.empty()){ //当首次访问时树不为空,栈不为空时,循环 if(p != NULL){ st.push(p); p = p->lchild; }else{ BiTree q; q = st.pop(); visit(q.data); p = q->rchild; } } }
思考:如何实现非递归的先序和后序遍历
void LevelOrder(BiTree T){ BNode* p = T; if(p == NULL){ exit(0); //空二叉树 } queue que; que.push(p); while(!que.empty()){ p = que.pop(); visit(p); if(p->lchild != NULL){ que.push(p->lchild); } if(p->rchild != NULL){ que.push(p->rchild); } } }
对于建立字符ABCDEGF二叉树,需插入特殊字符来使该序列建立的二叉树唯一。
如:ABC##DE#G##F###
先序遍历(递归构造)
Status CreateBiTree(BiTree &T){
cin>>ch; //scanf(&ch)
if(ch == '#'){
T = NULL;
}else{
if(!T = (BiTNode*)malloc(sizeof(BiTNode))){ //T = new BiTNode;
exit(0); //分配内存失败
}
T->data = ch; //生成根结点
CreateBiTree(T->lchild); //构造左子树
CreateBiTree(T->rchild); //构造右子树
}
return OK;
}
int Copy(BiTree T, BiTree &NewT){
if(T == NULL){
NewT = NULL;
return 0;
}else{
NewT = new BiTNode;
NewT->data = T->data;
Copy(T->lchild, NewT->lchild);
Copy(T->rchild, NewT->rchild);
}
return 1;
}
int Depth(BiTree T){
if(T == NULL){
return 0;
}else{
int m = Depth(T->Lchild);
int n = Depth(T->Rchild);
if(m > n){
return m+1;
}else{
return n+1;
}
}
}
int NodeCount(BiTree T){
if(T == NULL){
return 0;
}else{
return NodeCount(T->lChild)+NodeCount(T->rChild)+1;
}
}
int LeafCount(BiTree T){
if(T == NULL){
return 0;
}
if(T->lChild == NULL && T->rChild == NULL){
return 1;
}else{
return LeafCount(lChild)+LeafCount(rChild);
}
}
提出的问题:如何寻找特定遍历二叉树结点的前驱和后继?
解决方法:1、再遍历寻找---------费时间
2、再 增设前驱、后继指针域---------增加存储负担
3、利用二叉链表中的空指针域
二叉链表中空指针域的数量:
具有n个结点的二叉链表中,共2n个指针域,因为n个结点有n-1个孩子(n-1个边,即指针),所以剩下n+1个空指针域。
利用二叉链表中的空指针域:
如果某结点的左孩子为空,则将其指向其前驱;如果右孩子为空,则指向其后继
----------这种改变指向的指针称为**线索**
加上了线索的二叉树称为线索二叉树(Threaded Binary Tree)
//线索二叉树的结点结构
typedef struct BiThrNode{
int data;
int ltag;//ltag=0--->lchild指向该结点的左孩子,ltag=1则指前驱
int rtag;//rtag=0--->rchild指向该结点的右孩子,rtag=1则指后继
struct BiThrNode*lchild,rchild;
}
实现:定义结构数组,存放树的结点,每个结点含两个域:
//树结点
typedef struct PTNode{
TElemType data;
int parent; //双亲位置域
}PTNode;
//树结构
#define MAX_TREE_SIZE 100
typedef struct{
PTNode nodes[MAX_TREE_SIZE];
int r,n; //根结点的位置和结点个数
}PTree;
把每个结点的孩子结点排列起来,看成一个线性表,用单链表存储,则n个结点有n个孩子链表(叶子结点的孩子链表为空表)。而n个头指针又组成一个线性表,用顺序表(含n个元素的结构数组)存储。
//孩子结点结构
typedef struct CTNode{
int child;
struct CTNode *next;
}*ChildPtr;
//双亲结点结构
typedef struct{
TElemType data;
ChildPtr firstchild; //孩子链表头指针
}CTBox;
//树结构
typedef struct{
CTBox nodes[MAX_TREE_SIZE];
int n,r; //结点数和根结点的位置
}
(二叉树表示法,二叉链表表示法)
实现:用二叉链表作树的存储结构,链表中每个结点的两个指针域分别指向其第一个孩子结点和下一个兄弟结点
//结点结构
typedef struct CSNode{
ElemType data;
struct CSNode *firstchild,*nextchild;
}CSNode,*CSTree;
口诀:兄弟相连留长子
口诀:树变二叉根相连
口诀:去掉全部右孩线,孤立二叉再还原
先序遍历:若树不空,则
1、访问森林中第一棵树的根结点
2、先序遍历森林中第一棵树的子树森林
3、先序遍历森林中(除第一棵树之外)其余树构成的森林
即:依次从左至右对森林中的每一棵树进行先根遍历
中序遍历:若树不空,则
1、中序遍历森林中第一棵树的子树森林
2、访问森林中第一棵树的根结点
3、中序遍历森林中(除第一棵树之外)其余树构成的森林
即:依次从左至右对森林中的每一棵树进行后根遍历
路径:从树中一个结点到另一个结点之间的分支构成这两个结点间的路径
结点的路径长度:两结点路径上的分支数
树的路径长度:从树根到每一个结点的路径长度之和,记作:TL
(结点数目相同的二叉树中,完全二叉树是路径长度最短的二叉树)
权(weight):将树中结点赋给一个有着某种含义的数值,这个数值称为该结点的权
结点的带权路径长度:从根结点到该结点之间的路径长度与该结点的权的乘积
树的带权路径长度:树中所有叶子结点的带权路径长度之和
->哈夫曼树:最优树(带权路径长度(WPL)最短的树)
注:“带权路径长度最短”是在“度相同”的树中比较而得到的结果
邻接矩阵的存储表示:用两个数组分别存储顶点表和邻接矩阵
#define MVNum 100 //最大顶点数
#define INF 0xff
typedef struct{
int vexs[MVNum]; //顶点表
int arcs[MVNum][MVNum]; //邻接矩阵
int vexnum,arcnum; //图当前的顶点数和边数
}AMGraph;
创建无向网
void CreateUDN(AMGraph &G){ cin >> G.vexnum >> G.arcnum; for(int i=0;i<G.vexnum;++i){ cin>>G.vexs[i]; //输入顶点信息 } for(int i=0;i<G.vexnum;++i){ for(int j=0;j<G.vexnum;++j){ G.arcs[i][j]=INF; //边的初始值设为最大值 } } for(int k=0;k<G.arcnum;++k){ cin >> v1 >> v2 >> w; //v1,v2代表的是顶点表中的顶点 int i = LocateVex(G,v1);//查找v1顶点的下标 int j = LocateVex(G,v2);//查找v2顶点的下标 G.arcs[i][j]=w; G.arcs[j][i]=G.arcs[i][j];//无向图对称 } }
顶点的顶点结构
typedef struct VNode{
int data; //顶点信息
ArcNode* firstarc; //指向第一条依附该顶点的边的指针
}VNode, AdjList[MVNum]; //AdjList表示邻接表类型
//PS: AdjList v <=> VNode v[MVNum]
弧(边)的结点结构
typedef struct ArcNode{
int adjvex; //该边所指向的顶点的位置(即哪一个顶点的边,adjList的下标)
struct ArcNode* next; //指向下一条边的指针
int weight; //和边相关的信息,如权值
}ArcNode;
图的结构定义
typedef struct{
AdjList vertices; //即头顶点数组
int vexnum,arcnum; //图的当前顶点数和边数
}ALGraph;
创建无向网
void CreateUDG(ALGraph &G){ cin >> G.vexnum >> G.arcnum; for(int i=0;i<G.vexnum;++i){ cin>>G.vertices[i].data; //输入顶点值 G.vertices[i].firstarc = NULL; //初始化表头指针域 } for(int k=0;k<G.arcnum;++k){ cin >> v1 >> v2; i = LocateVex(G,v1); j = LocateVex(G,v2); //找到下标 p1 = new ArcNode; //生成一个新的边结点p1 p1->adjvex = j; //邻点序号为j p1->next = G.vertices[i].firstarc; G.vertices[i].firstarc = p1; //头插法 p2 = new ArcNode; //生成另一个对称的新的边界点p2 p2->adjvex = i; p2->next = G.vertices[j].firstarc; G.vertices[j].firstarc = p2; //无向图对称操作 } }
void DFS(AMGraph G, int v){
cout << v;
visited[v] = true; //先访问第v个结点
for(int w=0;w<G.vexnum;++w){
if((G.arcs[v][w]!=0) && (!Visited[w])){
DFS(G,w);
}
}
}
void BFS(Graph G, int v){ cout << v; visited[v] = true; //先访问第v个结点 InitQueue(Q); //队列初始化 EnQueue(Q,v); //v进队 while(!QueueEmpty(Q)){ //队列非空循环 DeQueue(Q,u); //队头元素出队 for(int w=FirstAdjVex(G,u);w>=0;w=NextAdjVex(G,u,w)){ if(!visited[w]){ cout << w; visited[w] = true; EnQueue(Q,w); //w进队 } } } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。