线性表:
-
抽象数据类型定义:
ADT List{ 数据对象:D={ai|ai=ElemSet,i=1,2,..,n,n≥0} 数据关系:R1={<ai-1,ai>|ai-1,ai∈D,i=2,...,n} 基本操作: IniList(&L)操作结果:构造一个新的线性表L。 DestroyList(&L)操作结果:销毁线性表 ClearList(&L)操作结果:将L重置为空表 ListEmpty(L)操作结果:若L为空表,则返回TURE,否则返回FALSE ListLength(L)操作结果:返回L中数据元素个数 GetElem(L,i,&e)初始条件:线性表已存在,1≤i≤ListLength(L) 操作结果:用e返回L中第i个数据元素的值 LocateElem(L,e,compare())操作结果:返回L中第一个与e满足关系compare()的数据元素的位序。若这样的数据元素不存在,则返回结果为0. PriorElem(L,cur_e,&pre_e)若cur-e是L的数据元素,且不是第一个,则用pre_e返回它的前驱,否则操作失败,pre_e无定义 NextElem(L,cur_e,&text_e)若cur_e是L的数据元素,且不是最后一个,则用next_e返回它的后继,否则操作失败,next_e无定义 ListInsert(&L,i,e)初始条件:线性表已存在,1≤i≤ListLength(L)+1 操作结果:在L中第i个位置之前插入新的数据元素e,L的长度加1 ListDelete(&L,i,&e)初始条件: 线性表存在且非空,1≤i≤ListLength(L) 操作结果:删除L的第I个数据元素,并用e返回其值,L的长度减一 ListTravarse(L,visit())操作结果:依次对L的每个数据元素调用函数visit().一旦visit()失败,则操作失败。 }ADT List
顺序存储结构:
在内存中开辟一片连续存储空间(一维数组占用一片连续存储空间,故可用其进行表示)
-
定义:
#define MAXSIZE maxlen typedef int elemtype typedef struct { elemtype vec[maxlen]; int len; }sequenlist;
-
基本运算:
-
1.插入运算:
int insert(sequenlist *L,int i,elemtype x) { int j; if((*L).len>=maxlen) {printf("the list is overflow\n"); return NULL; } else if((i<1)||(i>(*L).len+1)) { printf("position is not corrent.\n"); return NULL; } else{ for(j=(*L).len-1;j>=i-1;j--) (*L).vec[j+1]=(*L).vec[j]; //元素后移 (*L).vec[i-1]=x; //插入x (*L).len++; //长度加一 return 1; } }
-
2.删除运算:
delete(sequenlist *L,int i) { int j; if((i<1)||(i>(*L).len)) { printf("delete fail\n"); return NULL; } else{ for(int j=i;j<=(*L).len-1;j++) (*L).vec[j-1]=(*L).vec[j]; (*L).len--; return 1; } }
-
链式存储结构(链表):
-
使用任意的存储单元进行表示,节点分为数据域和指针域(存放节点间相互关系),按不同的指针域组织方法可分为单链表,循环链表和双向链表.
单链表:
只含有一个指针域来存放下一个元素地址(即直接后继元素的存储地址)
-
描述
typedef int elemtype; typedef struct node //定义一个名为node的结构体 { elemtype data; //数据域 struct node *next; //指针域,该结构体的指针 }linklist;
-
基本运算
-
1.构建
linklist *creatlist(int n) //构建一个返回值为node结构体的指针 类型的函数 { int x,k; linklist *head,*r,*p; p=(linklist *)malloc(sizeof(linklist)); //构建头结点空间 head=p; p->next=NULL; r=p; for(k=1;k<=n;k++) //循环构建n个结点 { printf("input value:\n"); scanf("%d",&x); p=(linklist *)malloc(sizeof(linklist)); p->data=x; p->next=NULL; r->next=p; r=r->next; } return(head); }
-
-
2.单链表上元素的查找:
-
按序号查找:
linklist *find(linklist *head,int i) { int j; linklist *p; p=head->next; j=1; while((p!=NULL)&&(j<i)) { p=p->next; j++; } return p; }
-
按值查找:
-
linklist *locate(linklist *head,elemtype x)
{
linklist *p;
p=head->next;
while(p!=NULL&&p->data!=x)
p=p->next;
return p;
}
-
3.单链表的插入运算(按值插入和按序号插入):
-
后插入法(将值为x的新结点插入值为k的结点之后):
void insert(linklist *head,elemtype x,elemtype k) { linklist *p,*s,*q; s=(linklist *)malloc(sizeof(linklist)); //创建新结点 s->data=x; //为新结点赋值 p=head->next; if(p==NULL) //若为空表,则新结点为唯一结点 { head->next=s; s->next=NULL; } else{ q=head; while(p&&->data!=k) { q=p; p=p->next; } if(p!=NULL) //找到值为k的结点 { s->next=p->next; p->next=s; } else //没有找到值为k的结点,将新结点插入表尾 { q->next=s; s->next=NULL; } } }
-
前插:
-
将值为x的新结点插入值为k 的结点之前
void insert(linklist *head,elemtype x,elemtype k)
{
linklist *p,*s,*q;
s=(linklist *)malloc(sizeof(linklist)); //创建新结点
s->data=x; //为新结点赋值
p=head->next;
if(p==NULL) //若为空表,则新结点为唯一结点
{
head->next=s;
s->next=NULL;
}
else
{
q=head;
p=head->next;
while(p!=NULL)
{if(p->data!=k)
{q=p;
p=p->next;
}
else
break;
}
if(p!=NULL)
{
q->next=s;
s->next=p;
}
else //p==NULL,在表尾插入新结点
{
q->next=s;
s->next=NULL;
}
}
}
-
4.单链表的删除运算:
删除第i个结点
linklist *delete(linklist *head,int i) { linklist *p,*q,int j; p=head; j=0; while(p->next!=NULL&&j<i-1) { p=p->next; j++; } if(p->next!=NULL) { q=p->next; p->next=p->next->next; free(q); } else return NULL; return head; }
循环链表:
将单链表中最后一个结点的指针域改为存放链表中头结点(头指针指向的结点,其数据域可不存储信息),使其成为一个环,称为单链循环表(循环表)
其类型定义与单链表完全相同,操作也类似,只是判断链表结束的条件有所不同.
双向链表结构:
在结点中增加一个指针域指向其直接前驱,仅讨论双向循环链表,简称其为双向链表.
-
结点定义与实现:
typedef int elemtype; typedef struct dupnode { elemtype data; struct dupnode *next,*prior; }dulinklist;
-
基本操作:
-
插入运算(按值插入&&按序号插入):
将值为x的新结点插入到第i个结点之前
void insdulist(dulinklist *head,int i,elemtype x) { dulinklist *p,*s; int j; p=head; j=0; while((p->next!=head)&&(j<i-1)) { p=p->next; j++; } if(j==i-1) { s=(dulinklist *)malloc(sizeof(dulinklist)); s->data=x; s->prior=p; s->next=p->next; p->next->prior=s; p->next=s; } else printf("error\n"); }
-
删除运算(按序号删除&&按值删除):
按序号删除
void deledulist(dulinklist *head,int i) { dulinklist *p; int j; p=head; j=0; while((p->next!=head)&&(j<i)) { p=p->next; j++; } if(j==i) { p->prior->next=p->next; p->next->prior=p->prior; free(p); } else printf("error\n"); }
按值删除:
void dudelete(dulinklist *head,elemtype x) { dulinklist *p; p=head->next; while((p!=head)&&(p->data!=x)) p=p->next; if(p!=head) //找到结点 { p->prior->next=p->next; p->next->prior=p->prior; free(p); //删除结点后的空间回收 } else printf("have not find\n"); }
-
随机存取 顺序存取 随机存储 顺序存储
存取结构:分为随机存取和非随机存取(又称顺序存取)
1、随机存取就是直接存取,可以通过下标直接访问的那种数据结构,与存储位置无关,例如数组。非随机存取
就是顺序存取了,不能通过下标访问了,只能按照存储顺序存取,与存储位置有关,例如链表。
2、顺序存取就是存取第N个数据时,必须先访问前(N-1)个数据 (list),随机存取就是存取第N个数据时,
不需要访问前(N-1)个数据,直接就可以对第N个数据操作 (array)。
存储结构:分为顺序存储和随机存储
1.顺序存储结构
在计算机中用一组地址连续的存储单元依次存储线性表的各个数据元素,称作线性表的顺序存储结构。
顺序存储结构是存储结构类型中的一种,该结构是把逻辑上相邻的节点存储在物理位置上相邻的存储单元中,
结点之间的逻辑关系由存储单元的邻接关系来体现。由此得到的储结构为顺序存储结构,通常顺序存储结构是
借助于计算机程序设计语言(例如c/c++)的数组来描述的。
顺序存储结构的主要优点是节省存储空间,因为分配给数据的存储单元全用存放结点的数据(不考虑c/c++语言中数组需指定大小的情况),
结点之间的逻辑关系没有占用额外的存储空间。采用这种方法时,可实现对结点的随机存取,即每一个结点对应一个序号,
由该序号可以直接计算出来结点的存储地址。但顺序存储方法的主要缺点是不便于修改,对结点的插入、删除运算时,
可能要移动一系列的结点。
2、随机存储结构
在计算机中用一组任意的存储单元存储线性表的数据元素(这组存储单元可以是连续的,也可以是不连续的)。
它不要求逻辑上相邻的元素在物理位置上也相邻。因此它没有顺序存储结构所具有的弱点,但也同时失去了顺序表可随机存取的优点。
栈和队列
1.栈(后进先出的线性表,LIFO表)Last In First Out
抽象数据类型:
ADT Stack {
void inistack(&s) //将s置为空栈
void Push(&s,x) //入栈
void Pop(&s) //退栈
elemtype gettop(&s) //取栈s中的栈顶元素
int empty(&s) //判断栈是否为空
}ADT stack;
顺序栈:
-
类型描述:
#define MAXNUM 100 //定义栈的最大容量 typedef struct{ elemtype stack[MAXNUM]; int top; //指示栈顶位置 }seqstack; seqstack *s;
-
运算:
-
1.初始化栈:
void inistack(seqstack *s) { s->top=-1; }
-
2.进栈:
void push(seqstack *s,elemtype x){ if (s->top==MAXNUM-1) print("overflow"); else { s->top++; s->stack[s->top]=x; } }
-
3.退栈:(出栈时,top向下移动,但数据元素不会自动消失,只有当新元素进栈时才能将其覆盖)
eletype pop(seqstack *s) { if(s->top<0) { print("underflow"); return(null); } else { s->top--; return(s->stack[s->top+1]); } }
-
4.取栈顶元素:
eletype gettop(seqstack *s) { if(s->top<0) { print("underflow"); return(null); } else { return(s->stack[s->top]); } }
-
5.判栈空否:
int empty(seqstack *s) { if (s->top<0) return 1; else return 0; }
栈的共享存储单元(充分利用存储单元):
-
类型描述:
#define m maxlen typedef struct { elemtype stack[m]; int top[2]; }duseqstack;
-
初始化算法:
void inistack(deseqstack *s) { s->top[0]=-1; s->top[1]=m; }
-
进栈:
int push(duseqstack *s,elemtype x,int i) //使元素x进入到以s为栈空间的第i个栈中 { if(s->stack[0]==(s->stack[1]-1) { printf("overflow"); return 0; } if(i!=0||i!=1) { printf("栈参数出错"); return 0; } if(i==0) { s->top[0]++; s->stack[s->top[0]]=x; } else { s->top[1]--; s->stack[s->top[1]]=x; } return 1; }
-
退栈:
elemtype pop(duseqstack *s,int i) //对以s为栈空间的栈中第i个栈进行退栈 { if(i!=0||i!=1) { printf("栈参数出错"); return 0; } if(i==0) { if(s->top[0]<0) { printf("0栈已空"); return 0; } else { s->top[0]--; return (s->stack[s->top[0]+1]); } } else if(s->top[1]==m) { printf("1栈已空"); return 0; } else { s->top[1]++; return (s->stack[s->top[1]-1]); } }
-
链栈:
1.结构及数据类型:
typedef struct node{
elemtype data;
struct node *next;
}linkstack;
linkstack *top; //链栈头指针
2.链栈的5种运算:
(1)初始化:
linkstack *inistack(){
linkstack *top;
top=(linkstack *)malloc(sizeof(linkstack)); //将malloc开辟动态内存的指针强制转换为linkstack类型
if(top==NULL)
exit(1); //异常退出,返回1给操作系统
top->next=NULL;
return top;
}
(2)进栈运算:
void push(linkstack *top,elemtype x){
linkstack *p;
p=(linkstack *)malloc(sizeof(linkstack));
p->data=x;
p->next=top->next;
top->next=p;
}
(3)退栈运算:
elemtype pop(linkstack *top){
linkstack *p;
elemtype x;
p=top->next; //p指向当前栈顶元素
if(p==NULL){printf("栈已空");return (NULL);}
else
{
top->next=p->next;
x=p->data;
free(p);
return(x);
}
}
(4)取栈顶元素:
elemtype gettop(linkstack *top){
if(top->next!=NULL)
return(top->next->data);
else
return(NULL);
}
(5)判栈空否:
int empty(linkstack *top){
if(top->next!=NULL)
return 0;
else
return 1;
}
栈的应用:
-
1.算术表达式的求值:
后缀表达式(逆波兰表达式):每个操作符出现在它的操作数后面(A*B/C变为AB**C/)
编译系统中表达式的计算: 1.把中缀表达式变为后缀表达式 2.根据后缀表达式计算表达式的值
-
2.数据转换问题:
非负十进制整数N转换为B进制数
void convert(int n,int b){ int k;seqstack *s; inistack(s); while(n!=0){ push(s,n%b); n=n/b; } while(!empty(s)){ printf("%d",pop(s)); } }
-
3.函数的嵌套调用:
-
4.函数的递归调用:
2.队列(操作受限的线性表):
1.定义(FIFO)First in First Out:
队尾(Rear):只允许进行插入 --> 指针指向实际位置 **队首(Front)😗*只允许进行删除–>指针指向实际位置的前一个位置(方便运算)
2.基本运算:
- 初始化 INIQUEUE(&Q) 设置为空队列
- 入队列 ENQUEUE(&Q,X)
- 出队列 DEQUEUE(&Q) 删除队首元素
- 取队头元素 GETHEAD(Q)
- 判队空 EMPTY(Q)
顺序队列:
1.数据类型描述:
#define maxsize maxlen
typedef struct{
elemtype queue[maxsize];
int front;
int rear;
}seqqueue;
2.基本运算:
1.初始化:
void iniqueue(seqqueue *q)
{
q->front=-1;
q->rear=-1;
}
2.入队列:
int enqueue(seqqueue *q,elemtype x)
{
if(q->rear>=maxsize-1)
return false;
else
{
q->rear++;
q->queue[q->rear]=x;
return true;
}
}
3.出队列:
elemtype dlqueue(seqqueue *q)
{
if(q->rear==q->front) return (NULL);//队列为空
else
return (q->queue[++q->front]);
}
4.取队头:
elemtype gethead(seqqueue *q)
{
if(q->rear==q->front) return NULL;
else
return (q->queue[q->front+1]);
}
5.判队列非空:
int empty(seqqueue *q)
{
if(q->rear==q->front) return (TRUE);
else
return (FALSE);
}
6.求队列长度:
int length(seqqueue *q)
{
return(q->rear-q->front);
}
假溢出:
尾指针指向数组末尾,但前面还空出许多位置
解决方法:
- 每次出队列后,都将元素向前移动一个位置 (很少采用,花费大量时间)
- 循环队列
循环队列:
当rear等于9时,执行 rear=(rear+1)%maxsize
判别队列是否为空
(rear+1)%maxsize==front
运算实现:
1.初始化:
void INIQUEUE(seqqueue *q)
{
q->front=q->rear=maxsize-1;
}
2.进队列:
void enqueue(seqqueue *q,elemtype x)
{
if((q->rear+1)%maxsize==q->front)
printf("overflow");
else
{
q->rear=(q->rear+1)%maxsize;
q->queue[q->rear]=x;
}
}
3.出队列:
elemtype dlqueue(seqqueue *q)
{
if (q->rear==q->front)
{printf("underflow");return (NULL)}
else
{
q->front=(q->front+1)%maxsize;
return(q->queue[q->front]);
}
}
4.取队头元素:(得到的应为头指针后面的一个值)
elemtype gethead(seqqueue *q)
{
if(q->rear==q->front)
{printf("underflow")}
else
return(q->queue[(q->front+1)%maxsize]);
}
5.判队列空否:
int empty(seqqueue *q)
{
if(q->rear==q->front)
return 1;
else
return 0;
}
6.求队列长度:
int length(seqqueue *q)
{
return(q->rear-q->front+maxsize)%maxsize;
}
链队列:
抽象数据类型描述:
typedef struct node{
elemtype data;
struct node *next;
}queueptr; //定义链队列中的结点结构
typedef struct{
queueptr *front,*reat; //定义头指针和尾指针
}linkqueue;
linkqueue *q; //定义一个指向链队的指针
2.基本运算:
1.初始化:
void inilinkqueue(linkqueue *s){
queueptr *p;
s=(linkqueue *)malloc(sizeof(linkqueue));
p=(queueptr *)malloc(sizeof(queueptr));
p->next=NULL;
s->front=p;
s->rear=p;
}
2.入队列:
void enlinkqueue(linkqueue *s,elemtype x)
{
queueptr *p;
p=(queueptr *)malloc(sizeof(queueptr)); //创建一个新结点
p->data=x;
p->next=NULL;
s->rear->next=p;
s->rear=p;
}
3.出队列:
elemtype dllinkqueue(linkqueue *s)
{
queueptr *p;
elemtype x;
if(s->front==s->rear) //链队列为空,返回空值
return NULL;
else
{
p=s->front->next;
s->front->next=p->next;
if(s->front->next==NULL) //链队列只有一个元素时,出队列后尾指针也需修改
s->rear=s->front;
x->p->data;
free(p);
return(x);
}
}
4.判队空:
int empty(linkqueue *s)
{
if(s->front==s->rear)
return 1;
else
return 0;
}
5.取队头元素:
elemtype gethead(linkqueue *s)
{
if(s->front==s->rear)
return NULL;
else
return (s->front->next->data)
}
3.应用:
- 1.cpu资源的竞争问题
- 2.主机与外部设备之间速度不匹配的问题(缓冲区)
串和数组
串:
1.定义及运算:
a.基本概念:
- 1.定义:
由零个或多个字符组成的有限序列,记作s=“a1a2a3…” - 2.空串:不含任何字符
- 3.空格串:含有一个或多个空格的串
- 4.子串:串中任意连续字符组成的子序列 主串:包含该子串的串
b.串的运算;
-
1.连接:StrConcat(S,T)
-
2.求串的长度:StrLength(T)
-
3.串比较大小:StrCmp(S,T)
-
4.串赋值:StrAssign(s1,s2(串常量或串变量))
-
5.求子串:SubStr(s,i,len)
返回从串s的第i个字符开始的长度为len的子串
-
6.子串定位:StrIndex(s,t)
找子串t在主串s中首次出现的位置
-
7.串插入:StrInsert(s,i,t)
将串t插入到串s的第i个字符位置上
-
8.串删除:StrDelete(s,i,len)
前五个操作最基本,不能用其他的操作合成,称为最小操作集
2.存储结构:
顺序存储(顺序串):(使用更为广泛)
-
紧缩存储和非紧缩存储:(字编址)
紧缩存储:尽可能的将多个字符放在一个字中(节约空间,不便操作)
非紧缩存储:一个存储单元只存储一个字符(浪费空间,便于操作)
- 字节存储:(字节编址)
链式存储:
结点有两个域:data域存放字符,next域存放指向下一个结点的指针
-
结点大小为1的链式存储:
每个结点为一个字符
-
结点大小为k的链式存储:
结点有k个数据域和一个指针域
3.串的基本运算:
数据类型描述:
const int maxlen=100;
typedef struct str
{
char ch[maxlen];
int len;
}string;
1.初始化:
void clearstring(string *s)
{
for(int i=0;i<maxlen;i++)
(*s).ch[i]='';
(*s).len=0;
}
2.串的连接:
void StrConcat(string *t,string *s1,string *s2){
int i=0,j=0,k=0;
if((*s1).len + (*s2).len >=maxlen-1 )
printf("t串太长");
else{
while((*s1).ch[j] != '\0')
(*t).ch[i++] =(*s1).ch[j++];
while((*s2).ch[k] != '\0')
(*t).ch[i++] = (*s2).ch[k++];
(*t).ch[i]='\0';
(*t).len = (*s1).len + (*s2).len;
}
}
3.求子串:
void substring(string *t,string *s,int pos,int len){
//用t返回主串s地pos个字符起,长度为len的子串
int i=0,j=0;
for(j=pos;j<=pos+len-1;j++)
{
(*t).ch[i]=(*s).ch[j];
i++;
}
}
4.串比较:
int StrCmp (char s1[],char s2[]){
int k=0;
while((s1[k] !='\0') || (s2[k] !='\0'))
{
if (s1[k]>s2[k])
return(s1[k]-s2[k]);
else if(s1[k]<s2[k])
return(s1[k]-s2[k]);
k++;
}
if( (s1[k]=='\0') && (s2[k]=='\0'))
return 0;
}
数组:
一维数组:可看作一个线性表或向量
n维数组:非线性结构,有n个直接前驱和n个直接后驱
数组的存储结构(二维):
1.行优先顺序:
按行号有小到大地顺序,先将第一行中的元素全部存放好,再存放第二行元素,第三行元素,以此类推(BASIC C/C++)
地址计算:LOC(aij)=LOC(a00) + (i*n+j) * c 每个元素占c 个存储单元
2.列优先顺序:
按列号从小到大的顺序,先将第一列中地元素存放好,再存放第二列元素,第三列元素,以此类推
特殊矩阵及其存储压缩 :
对称矩阵(行优先存放):
- 1.只存放下三角部分 只需存放主对角线及主对角线以下的元素
- 2.只存放上三角部分
三角矩阵:
与对称矩阵相似,但多一个存储单元存放上(下)三角部分元素,使用地存储单元数目为 n*(n+1)/2 +1
对角矩阵:
所有非零元素都集中在以主对角线为中心的带状区域中
稀疏数组:
定义:矩阵非零元素个数t<<m*n
1.三元组表示:
仅存放非零元素,将非零元素的行,列及他的值构成一个三元组(i,j,v),然后按某种规律存储这些三元组,合成三元组表
数据类型描述:
const int mansize =100; //定义非零元的最大数目
struct node {
int i,j; //非零元行列号
int v; //非零元值
}
struct sparmatrix{ //定义稀疏矩阵
int rows,cols; //稀疏矩阵行列数
int terms; //稀疏矩阵非零元个数
struct node data [maxsize]; //三元组表
}
2.十字链表表示:
结点:
row:非零元素的行号 col:非零元素的列号 v:本元素的值 right(行指针域):指向本行中下一个非零元素 down(列指针域):指向本列中下一个非零元素
数据结构类型描述:
struct linknode{
int i,j;
linknode *dowm,*right;
union vnext{
int v; //定义一个共用体 表结点使用v域 表头结点使用next域
linknode *next;
} k;
}
树
1.基本概念:
定义:
- 有且仅有一个结点没有直接前驱,即根结点
- 除根节点外,其余所有结点有且仅有一个直接前驱结点
- 包括根节点在内,每个结点可以有多个直接后继结点
基本术语:
- 结点的度:一个结点包含子数的个数
- 树叶(叶子):度为0的结点,称为叶子结点或树叶,也叫终端结点
- 分支结点(非终端结点):除叶子节点外的所有结点,除根节点外的分支结点称为内部结点
- 孩子结点:若结点x有子树,则子树的根结点为x的孩子结点
- 双亲结点:若结点x有子女y,则x为y的双亲结点
- 祖先结点:一个结点的祖先是从根结点到该结点路径上所经过的所有结点
- 子孙结点:某一结点的直接后继和间接后继
- 兄弟结点:具有同一个双亲的结点,不具有同一个双亲但具有共同祖先的结点是堂兄弟
- 树的高度:树中结点所处的最大层数
- 树的度:树中结点度的最大值
- 有序树:一棵树中所有子树从左到右的排列是有顺序的,不能颠倒次序
- 无序树:一棵树中所有子树的次序无关紧要
- 森林:有限棵互不相交的树组成的集合
2.树的存储结构:
多重链表表示法:
1.定长结点的多重链表表示法:
取树的度数作为每个结点的指针域的个数
2.不定长结点的多重链表表示法:
每个结点都采用自己的度数作为指针域的个数,同时,还应增设一个度数域用来指出该结点的度数
二重链表表示法:
每个结点设一个数据域和2个指针域,第一个指针域用来给出左边第一个长子的地址,第二个指针域用来给出右边第一个兄弟结点的地址
3.二叉树:
定义:二叉树是由n个结点的有限集合,该集合为空集或由一个称为根的结点及两棵不相交的左子树和右子数组成
满二叉树:深度为k具有2^k-1个结点的二叉树
完全二叉树:如果一棵具有n个结点的深度为k的二叉树,它的每一个结点都与深度为k的满二叉树中编号为1-n的结点一一对应,则为完全二叉树
存储结构:
1,顺序存储结构
按照二叉树的结点从上至下,从左到右的结点层次编号依次来存放。普通二叉树需要改造为满二叉树或完全二叉树进行存放
2.链式存储结构:
表示:一个数据域,两个指针域
数据类型描述:
typedef struct btnode{
int data;
struct btnode *lchild,*rchild;
}bitree;
基本操作:
-
- Initiate(root) :初始化,建立一棵空二叉树
-
- Create(x,lchild,rchild) :生成一棵有左右子树的二叉树
-
- InsertL (x, parent):将x插入parent的左结点,若其原来就含有左结点,则将原来的左孩子作为结点x的左孩子
-
- InsertR (x,parent) :将x插入parent的右结点 同上
- DeleteL (root, parent ) :在二叉树root中删除节点为parent的左子树
- DeleteR (root, parent ) :在二叉树root中删除节点为parent的右子树
- Search (root,x) :在二叉树root中查找数据元素x
- Traverse (root) :按某种方式遍历二叉树root的全部结点
算法实现:
链表存储结构
1.Initiate (root):
void Initiate(bitree *root){ //初始化二叉树,root指向空结点
root = (bitree *)malloc(sizeof(bitree));
root->rchild=NULL;
root->lchild=NULL;
}
2.Create (x,lchild,rchild)
bitree *Create(elemtype x,bitree *lchild,bitree *rchild){
//生成一棵以x为结点,以lchild和rchild为左,右子树的二叉树
bitree *root;
root=(bitree *)malloc(sizeof(bitree));
root->data=x;
root->lchild=lchild;
root->rchild=rchild;
return root;
}
3.InsertL (x,parent)
bitree *Insert (elemtype x,bitree *parent){
bitree *p;
if(parent=NULL){
printf("\n输入出错");
return NULL;
}
p=(bitree *)malloc(sizeof(bitree));
p->data=x;
p-lchild=NULL;
p-rchild=NULL;
if(parent->lchild=NULL)
parent->lchild=p;
else
{
p->lchild=parent->lchild;
parent->lchild=p;
}
return p;
}
4,DeleteL(parent)
bitree *Delete(bitree *parent){
bitree *p;
if(parent==NULL||parent->lchild==NULL)
{
printf("\n删除出错");
return NULL;
}
p=parent->lchild;
parent->lchild=NULL;
free(p);
return parent;
}
遍历二叉树:
含义:遵从某种次序,访问二叉树中的所有结点
DLR(先序或前序遍历) DRL(逆先序遍历)
LDR(中序遍历) RDL(逆中序遍历)
LRD(后序遍历)RLD(逆后序遍历)
先序遍历:
void preorder(bitree *root){
if(root!=NULL){
printf("%d",root->data);
preorder(root->lchild);
preorder(root->rchild);
}
}
中序遍历:
void inorder(bitree *root){
if(root!=NULL){
inorder(root->lchild);
printf("%d",root->data);
inorder(root->rchild);
}
}
后序遍历:
void postorder(bitree *root){
if(root!=NULL){
postorder(root->lchild);
postorder(root->rchild):
printf("%d",root->data);
}
}
非递归算法:
先序遍历:
void preorder(bitree *root){
bitree *p,*s[100];
int top=0; //top为栈顶指针
p=root;
do{
while(p!=NULL){
printf("%d",p->data);
if(p->rchild!= NULL)
s[top++]=p->rchild;
p=p->lchild;
}
if(top>=0)
p=s[--top];
}while(top>=0);
}
中序遍历:
void inorder(bitree *root){
bitree *p,*s[100];
int top=0;
p=root;
do{
while(p!=NULL){
s[top++]=p;
p=p->lchild;
}
if(top>=0){
p=s[--top];
printf("%d",p->data);
p=p->rchild;
}
}while(top>=0);
}
后序遍历:
其出栈条件有两种情况:
-
栈顶元素所指向的节点的左子树和右子树均为空;
-
栈顶元素所指向的节点的左子树和右子树均被访问过。
第一种情况比较好判断,但是对于第二种情况就比较麻烦一点。要先判断其左子树和右子树是否分别被访问过。
对于第二种情况,多加一个指针指向上一次循环访问过的节点(如果栈顶元素指向节点的左子树或右子树的值与该指针的值相等,则说明该栈顶元素可以出栈),并且进栈顺序是先进右子树,再进左子树,这样当右子树被访问过时,其左子树一定已经被访问过了。
void postorder1(bitree *root){
bitree *p,*s1[100];
int s2[100],top=0,b;
p=root;
do{
while(p!=NULL){
s1[top]=p;
s2[top++]=0;
p=p->lchild;
}
if(top>0){
b=s2[--top];
p=s1[top];
if(b=0){
s1[top]=p;
s2[top++]=1;
p=p->rchild;
}
else{
printf("%d",p->data);
p=NULL;
}
}
}while(top>0);
}
由二叉树的前序序列和中序序列可以建立该二叉树
层次遍历:
void lorder(bitree *root){
bitree *q[maxsize],*p;
int f,r;
q[0] = root;
f=r=0;
while(f<=r){
p=q[f];
f++;
printf("%d",p->data);
if(p->lchild != NULL){
r++;
q[r]=p->lchild;
}
if(p->rchild != NULL){
r++;
q[r]=p->rchild;
}
}
按前序序列建立二叉树:
bitree *creatbitree(){
bitree *t;
char ch;
printf("%c",ch);
if(ch==' ')
t=NULL;
else
{
t=(bitree *)malloc(sizeof(bitree));
t->data=ch;
t->lchild=creatbitree();
t->rchild=creatbitree();
}
return t;
}
查找数据元素:
bitree *Search(bitree *root,elemtype x){
bitree *p=NULL;
if(root != NULL){
if(root->data==x)
p=root;
else{
p=Search(root->lchild,x);
if(p==NULL)
p=Search(root->rchild,x);
}
}
return p;
}
线索二叉树:
指向直接前驱结点和指向直接后驱结点的指针被称为线索,加了线索的二叉树被称为线索二叉树
线索化的过程就是在遍历的过程中修改空指针的过程
ltag ? lchild域指向某种遍历下的直接前驱 : lchild域指向结点的左孩子
此时每个结点有五个域:
lchild ltag data rtag rchild
结点数据类型描述:
typedef struct bithrnode{
int data;
int rtag,ltag;
struct bithrnode *lchild,*rchild;
}binode;
树和森林:
树,森林和二叉树的转换:
树转换为二叉树
1.连线(兄弟结点间) 2.抹线(只留每个结点域左孩子的连线) 3.旋转
森林转化为二叉树:
1.森林中的每棵树转化为二叉树
2.n棵树成为n-1棵树的右子树,n-1棵树成为n-2棵树的右子树 。。。
树和森林的遍历:
树的遍历:
1.先序遍历:
若树非空,则先访问根节点,然后按照从左往右的顺序先序遍历各个子树
2.后序遍历:
若树非空,则依次后序遍历各个子树,最后访问根结点
森林的遍历:
1.先序遍历:
依次先序遍历各棵树
2.后序遍历:
按后序遍历森林中的每一棵树
树和森林的先序遍历与其转化后的二叉树的先序遍历相同,后序遍历与其转化后的二叉树的中序遍历相同
哈夫曼树(最优二叉树):
基本术语:
- 路径:一个结点到另一个结点间的路径
- 路径长度:路径上分支的数目或路径上边的数目
- 树的路径长度:从树根到每个结点的路径长度之和,一般记为PL
- 结点的带权路径长度:从根节点到该结点之间的路径长度与该结点的权的乘积
- 树的带权路径长度(wpl):树中的所有叶子结点的带权路径长度之和
定义:带权路径长度最小的二叉树,也称为最优二叉树
哈夫曼树的构造:
1.将n个叶子结点排列为森林,选取其中最小的两棵树作为一颗树的左右结点,根结点为两个结点权值之和
2.重复上面的过程,直到只剩一棵树
哈夫曼树的应用:
1.哈夫曼编码:
前缀编码:任一编码都不是另一个字符编码的前缀
可用哈夫曼数来设计二进制前缀编码,将每个元素按其概率作为权值构造哈夫曼树,规定左分支用字符‘0’表示,右分支用分支‘1’表示,则叶子结点字符编码为从根结点到叶结点所经过的路径分支组成的0和1的序列
2.在判定问题中的应用
图:
基本概念:
定义:
一个图G是由顶点集V和顶点间的关系集合边的集合E组成的一种数据结构
G(V,E) V={vi | vi属于dataobject}E={(vi,vj) | 表示顶点i和j之间有一条直线连线}
相关术语:
1.无向图:顶点i和j之间的连线没有方向性 用(x,y)表示
无向图:顶点i和j之间的连线有方向性 用<x,y>表示从x到y
2.顶点的度:与该顶点相关联的边的数目,在有向图中,以顶点V为终点边的数目称为V的入度,以顶点V为始点边的数目称为V的出度
3.权:在图的边或弧中给出相关的数值,称为权,带权图一般称为网
4.图的连通与不连通:在无向图中,若顶点x到y有路径,则称x到y是连通的,若图中任意两个顶点都是连通的,则称此无向图为连通图
5.无向图中,极大的连通子图(尽量把图的所有结点连接起来的子图)为该图的连通分量,非连通图有多个分量
在有向图中,若任意两个节点都相互连通,则称此有向图为强连通图
6,有向图中,极大的强连通子图称为该图的强连通分量
图的存储结构:
邻接矩阵:
设G=(V,E)是具有n个顶点的图,则其邻接矩阵是一个n阶方阵,若(x,y)有边,则对应矩阵元素置1
数据类型描述:
#define n maxn
#define e maxe
typedef struct {
elemtype v[n+1]; //存放顶点信息v1,v2,v3.....不使用v[0]存储空间
int arcs[n+1][n+1];
}graph;
无向图邻接矩阵:
void creatadj (graph *g){
int i,j,k;
for(k=1; k<=n; k++)
scanf("%d",&g->v[k]); //输入顶点信息
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
g->arcs[i][j]=0; //邻接矩阵初始化
for(k=1;k<=e;k++){
scanf("%d%d",&i,&j); //读入e条边
g->arcs[i][j]=1;
g->arcs[j][i]=1;
}
}
有向图的邻接矩阵:
void creatadj (graph *g){
int i,j,k;
for(k=1; k<=n; k++)
scanf("%d",&g->v[k]); //输入顶点信息
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
g->arcs[i][j]=0; //邻接矩阵初始化
for(k=1;k<=e;k++){
scanf("%d%d",&i,&j); //读入边
g->arcs[i][j]=1;
}
}
无向网的邻接矩阵:
void creatadj (graph *g){
int i,j,k;
float w;
for(k=1; k<=n; k++)
scanf("%d",&g->v[k]); //输入顶点信息
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
g->arcs[i][j]=0; //邻接矩阵初始化
for(k=1;k<=e;k++){
scanf("%d%d%f",&i,&j,&w); //读入e条边
g->arcs[i][j]=w;
g->arcs[j][i]=w;
}
}
有向网的邻接矩阵:
void creatadj (graph *g){
int i,j,k;
float w;
for(k=1; k<=n; k++)
scanf("%d",&g->v[k]); //输入顶点信息
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
g->arcs[i][j]=0; //邻接矩阵初始化
for(k=1;k<=e;k++){
scanf("%d%d%f",&i,&j,&w); //读入e条边
g->arcs[i][j]=w;
}
}
邻接表:
定义:
为图中每个顶点建立一个单链表,对应于邻接矩阵的一行。每个顶点可以得到若干个单链表,每个单链表有一个头结点,所有头结点组成一个一维数组,称这样的链表为邻接表
数据类型描述:
#define n maxn //图中最大顶点数
#define e maxe //图中最大边数
struct link { //定义单链表类型,即链表中的结点
elemtype data;
link *next;
};
struct node { //邻接表的表头类型
elemtype v;
link *next;
};
struct node a[n+1];
无向图邻接表建立:
void creatlink(){
int i,j,k;
link *s;
for(i=1;i<=n;i++){
a[i].v=i;
a[i].next=NULL; //建立邻接表头结点
}
for(k=1;i<=e;k++){
scanf("%d%d",&i,&j);
s=(link *)malloc(sizeof(link));
s->data=j;
s->next=a[i].next;
a[i].next=s;
s=(link *)malloc(sizeof(link));
s->data=i;
s->next=a[j].next;
a[j].next=s;
}
}
有向图邻接表建立:
void creatlink(){
int i,j,k;
link *s;
for(i=1;i<=n;i++){
a[i].v=i;
a[i].next=NULL; //建立邻接表头结点
}
for(k=1;i<=e;k++){
scanf("%d%d",&i,&j);
s=(link *)malloc(sizeof(link));
s->data=j;
s->next=a[i].next;
a[i].next=s;
}
}
网的邻接表数据类型描述:
#define n maxn
#define e maxe
struct link{
elemtype data;
float w;
link *next;
};
struct node{ //邻接表的表头类型
elemtype v;
link *next;
}a[n+1];
无向网的邻接表建立:
void creatlink(){
int i,j,k;
float w1;
link *s;
for(i=1;i<=n;i++){
s[i].v=i;
s[i].next=NULL;
}
for(k=1;k<=e;k++){
scanf("%d%d%d",&i,&j,&w1);
s=(link *)malloc(sizeof(link));
s->data=j;
s->w=w1;
s->next=a[i].next; a[i].next=s;
s=(link *)malloc(sizeof(link));
s->data=i;
s->w=w1;
s->next=a[j].next; a[j].next=s;
}
}
有向网的邻接表建立:
void creatlink(){
int i,j,k;
float w1;
link *s;
for(i=1;i<=n;i++){
s[i].v=i;
s[i].next=NULL;
}
for(k=1;k<=e;k++){
scanf("%d%d%d",&i,&j,&w1);
s=(link *)malloc(sizeof(link));
s->data=j;
s->w=w1;
s->next=a[i].next; a[i].next=s;
}
}
图的遍历:
1.深度优先搜索遍历:
邻接矩阵实现:
void dfs(graph *g,int i){ //从顶点i遍历
int j;
printf("%d",g->v[i]); //输出访问结点
visited[i]=1; //全局数组访问标志置1表示已经访问
for(j=1;j<=n;j++){
if((g->arcs[i][j]==1)&&(!visited[j]))
dfs(g,j);
}
}
邻接表实现:
void dfs1 (struct node a[n+1], int i){
link *p;
printf("%d",a[i].v);
visited[i]=1;
p=a[i].next;
while(p!=NULL){
if(!visited[p->data])
dfs1(a,p->data);
p=p->next;
}
}
非递归形式:
void dfs1 (struct node a[n+1],int i){
link *p;
int j,top=0,s[M];
printf("%d",a[i].v);
s[top]=i;
visited[i]=1;
do{
p=a[s[top]].next;
while((p!=NULL)&&visited[p->data])
p=p->next;
if(p==NULL) --top;
else{
i=p->data;
printf("%d",a[i].v);
visited[i]=1;
s[++top]=i;
}
}while(top>=0);
}
2.广度优先搜索遍历:
1.邻接矩阵实现:
void bfs(graph *g,int i){
int Q[n+1]; //Q为队列
int f,r,j; //f,r分别为队列头,尾指针
f=r=0;
printf("%d",g->v[i]);
visited[i]=1;
r++;
Q[r]=i;
while(f<r){
f++;
i=Q[f];
for(j=1;j<=n;j++){
if((g.arcs[i][j]==1)&&(!visited[j])){
printf("%d",g->v[j]);
visited[j]=1;
r++;
q[r]=j;
}
}
}
}
邻接表实现:
void bfs1(struct node a[n+1],int i){
int q[n+1],f,r;
link *p;
f=r=0;
printf("%d",a[i].v);
visited[i]=1;
r++;
q[r]=i;
while(f<r){
f++;i=q[f];
p=a[i].next;
while(p!=NULL){
if(!visited[p->data]){
printf("%d",a[p->data].v);
visited[p-data]=1; r++;
q[r]=p->data;
}
p=p->next;
}
}
}
非递归形式:
void dfs1 (struct node a[n+1],int i){
link *p;
int j,top=0,s[M];
printf("%d",a[i].v);
s[top]=i;
visited[i]=1;
do{
p=a[s[top]].next;
while((p!=NULL)&&visited[p->data])
p=p->next;
if(p==NULL) --top;
else{
i=p->data;
printf("%d",a[i].v);
visited[i]=1;
s[++top]=i;
}
}while(top>=0);
}
图的遍历:
1.深度优先搜索遍历:
邻接矩阵实现:
void dfs(graph *g,int i){ //从顶点i遍历
int j;
printf("%d",g->v[i]); //输出访问结点
visited[i]=1; //全局数组访问标志置1表示已经访问
for(j=1;j<=n;j++){
if((g->arcs[i][j]==1)&&(!visited[j]))
dfs(g,j);
}
}
邻接表实现:
void dfs1 (struct node a[n+1], int i){
link *p;
printf("%d",a[i].v);
visited[i]=1;
p=a[i].next;
while(p!=NULL){
if(!visited[p->data])
dfs1(a,p->data);
p=p->next;
}
}
非递归形式:
void dfs1 (struct node a[n+1],int i){
link *p;
int j,top=0,s[M];
printf("%d",a[i].v);
s[top]=i;
visited[i]=1;
do{
p=a[s[top]].next;
while((p!=NULL)&&visited[p->data])
p=p->next;
if(p==NULL) --top;
else{
i=p->data;
printf("%d",a[i].v);
visited[i]=1;
s[++top]=i;
}
}while(top>=0);
}
2.广度优先搜索遍历:
1.邻接矩阵实现:
void bfs(graph *g,int i){
int Q[n+1]; //Q为队列
int f,r,j; //f,r分别为队列头,尾指针
f=r=0;
printf("%d",g->v[i]);
visited[i]=1;
r++;
Q[r]=i;
while(f<r){
f++;
i=Q[f];
for(j=1;j<=n;j++){
if((g.arcs[i][j]==1)&&(!visited[j])){
printf("%d",g->v[j]);
visited[j]=1;
r++;
q[r]=j;
}
}
}
}
邻接表实现:
void bfs1(struct node a[n+1],int i){
int q[n+1],f,r;
link *p;
f=r=0;
printf("%d",a[i].v);
visited[i]=1;
r++;
q[r]=i;
while(f<r){
f++;i=q[f];
p=a[i].next;
while(p!=NULL){
if(!visited[p->data]){
printf("%d",a[p->data].v);
visited[p-data]=1; r++;
q[r]=p->data;
}
p=p->next;
}
}
}