数据结构基础知识回顾
- 1、数据结构概述
- 0.数据结构在学什么
- 1.数据结构的基本概念(抓大放小)
- 1)基本概念
- 2)数据结构的三要素
- 3)数据类型、抽象数据类型
- 2.算法的基本概念
- 1)什么是算法
- 2)算法的五大特性
- 3)“好算法”的特点
- 3.算法的时间复杂度
- 4.算法的空间复杂度
- 2、线性表
- 1.线性表的定义与基本操作
- 1)定义
- 2)基本操作
- 2.线性表的顺序表示
- 1)顺序表的定义
- 2)顺序表的实现
- 3)顺序表的四大特点
- 4)顺序表的基本操作
- 5)练习题
- 3.单链表
- 1)顺序表与链式表的比较
- 2)单链表定义
- 3)单链表的插入
- 4)单链表的删除
- 5)单链表的查找
- 6)单链表的建立
- 4.双链表
- 1)双链表的定义
- 2)双链表的插入
- 3)双链表的删除
- 4)双链表的遍历
- 5.循环链表
- 1)循环单链表
- 2)循环双链表
- 6.静态链表
- 1)静态链表的定义
- 2)静态链表的基本操作
- 3)静态链表的优缺点
- 7.顺序表与链表的对比
- 8.练习题
- 3、栈和队列
- 1.栈
- 1)栈的基本概念
- 2)栈的基本操作
- 3)顺序栈的实现
- 4)链式栈
- 2.队列
- 1)队列的基本概念
- 2)队列的顺序存储结构
- 3)队列的链式存储结构
- 3.栈和队列的应用
- 1)栈在括号匹配中的应用
- 2)栈在表达式中的应用
- 3)进制转换
- 4.串
- 1)串的基本操作
- 2)串的朴素模式匹配算法
- 4、树
- 1.二叉树
- 1)树的顺序存储
- 2)树的链式存储
- 3)二叉树的层次遍历
- 4)线索二叉树的构造
- 5)线索二叉树的遍历
- 6)平衡二叉树
- 5、图
- 1.图的存储结构
- 1)数组表示法
- 2)邻接表
- 3)十字链表法
- 2.图的遍历
- 1)广度优先遍历
- 2)深度优先遍历
- 3.最小生成树(prim算法)
- 4.最短路径
- 1)单源最短路径
- 5.拓扑排序
- 6、排序
- 1.插入排序
- 1)直接插入排序
- 2)折半插入排序
- 3)希尔排序
- 2.交换排序
- 1)冒泡排序
- 2)快速排序
- 3.选择排序
- 1)简单选择排序
- 2)堆排序
- 4.归并排序
- 5.快速排序
1、数据结构概述
0.数据结构在学什么
1.数据结构的基本概念(抓大放小)
1)基本概念
数据、数据元素(如一个账号)、数据项(密码、昵称)、数据结构(具有关系的一组数据元素集合,联想汉字的结构其实就是具有布局关系的符号组合)、数据对象(具有相同性质的数据元素的集合、一家海底捞的排队信息可以看作数据结构、全国所有门店排队信息看做同一个数据对象,它们虽无直接关系,但是具有同样的联系)
2)数据结构的三要素
逻辑结构、物理结构、数据运算
逻辑结构:集合、线性、树、图
物理结构:顺序存储、链式存储、索引存储(在存储数据的同时建立一张索引表,索引表中一般存放唯关键字+地址,比如海底捞中每个人取一个号)、散列存储(由关键字计算出其存储位置)。顺序存储数据是连续的,非顺序存储数据是离散的。数据存储的物理结构影响:增删与查找的便利度
数据运算的定义由逻辑结构定义,由物理结构实现。
3)数据类型、抽象数据类型
数据类型:一组同类型数据+操作,比如int类型数据,可以±*/,比如boolean类型,可以与或非操作。
数据类型分类:原子类型、结构类型(结构体)
抽象数据类型:抽象数据组织及其操作,即定义一个理论的数据类型,讨论其元素的逻辑关系,不讨论其实际物理结构。
2.算法的基本概念
1)什么是算法
程序=数据结构+算法,数据结构把现实生活中的信息存储到了计算机中,算法提供了解决实际问题的方法。比如海底捞的顾客排队信息用数据结构存储到了计算机中,那怎么实现vip插队就由算法来实现。
2)算法的五大特性
有穷性(程序可以无穷,比如海底捞排队系统只要不关机就一直运行)、确定性(无二义)、可行性(可由基本运算执行有限次实现)、0个或多个输入、1个或多个输出
3)“好算法”的特点
正确性(能够正确解决问题)、可读性、健壮性(非法输入可处理)、高效率低存储
3.算法的时间复杂度
算法的时间复杂度:预估运行时间与问题规模的关系T(n)。大O法表示,本质是:时间与问题规模的函数,不考虑低阶,不考虑系数,只考虑其数量级。
运算:加法规则、乘法规则(略)。
结论:
1.顺序执行的代码只影响常数项、忽略。
2.只需挑循环中的一个基本语句来判断其执行次数、分析时间复杂度即可。
3.多层嵌套只需关注最深层。
void loveYou(int flag[],int n)
{
int i;
for(i=0;flag[i]<n;i++)
{
if(flag[i]==n)printf("I love U");
break;
}
}
最好的情况:flag[0]等于n,最优时间复杂度O(1);
最坏的情况:要查找至最后一个数,最坏时间复杂度O(n);
平均情况:(1+2+…+n)/n=n*(n+1)/2,平均时间复杂度O(n)。
4.算法的空间复杂度
算法原地工作:算法的空间复杂度是常量。
void loveYou(int n;)
{
int flag[n];
int i;
}
数据所占内存:4+4n+4 B;空间复杂度:S(n)=O(n);
递归调用:
void loveYou(int n)
{
if(n>1) loveYou(n-1);
printf("I love U %d\n");
}
int main(void){
loveYou(5);
return 0;
}
每次执行loveYou需要使用k(常数)个存储空间存储数据,调用5次就会使用5k个存储空间,空间复杂度S(n)=O(n);
void loveYou(int n)
{
int flag[n];
if(n>1) loveYou(n-1);
printf("I love U %d\n");
}
int main(void){
loveYou(5);
return 0;
}
每一次执行loveYou占用4*(n+(n-1)+…1)+个空间存储数据[数组],S(n)=O(n^2);
2、线性表
1.线性表的定义与基本操作
1)定义
具有相同类型数据的有限序列(有次序)。表长、空表、位序(是线性表中第几个元素,从1开始)
2)基本操作
初始化:InitList(&L);
增加元素:ListInsert(&L,i,e);
删除元素:LisDelete(&L,i,&e);//用e返回删除元素的值,要把数据“带回来”,传地址,其实也可以传e,用return语句带回来
销毁:DestroyList(&L);
按值查找:LacateElem(L,e);
按位查找:GetElem(L,i);
输出:PrintList(L);
判空:Empty(L);
2.线性表的顺序表示
1)顺序表的定义
顺序存储实现的线性表。第一个a1元素位置,LOC(L);后续元素ai位置,LOC(L)+(i-1)*sizeof(ElemType)。
2)顺序表的实现
静态分配:
#define MaxSize 50
typedef struct{
int data[MaxSize];
int length;
}SqlList;
void InitList(SqlList &L)
{
int i;
for(i=0;i<L.length;i++) L.data[i]=0;
L.length=0;
}
int main(void)
{
SqlList L;
InitList(L);
int i;
for(i=0;i<L.length;i++)printf("%d ",L.data[i]);
return 0;
}
动态分配:
#include<stdio.h>
#include<stdlib.h>
#define InitSize 4 //默认长度
typedef struct{
int * data;
int MaxSize;
int length;
}SeqList;
void InitList(SeqList &L)
{
L.data=(int *)malloc(InitSize*sizeof(int));
L.length=0;
L.MaxSize=InitSize;
}
void IncreaseSize(SeqList &L,int len)
{
int * p=L.data;
L.data=(int *)malloc((L.MaxSize+len)*sizeof(int));
for(int i=0;i<L.MaxSize;i++) L.data[i]=p[i];
free(p);
L.MaxSize=L.MaxSize+len;
}
int main(void)
{
SeqList L;
InitList(L);
//这种插入数据的方式不规范,并没有改变length
L.data[0]=1;
L.data[1]=2;
L.data[2]=3;
L.data[3]=4;
for(int i=0;i<L.MaxSize;i++) printf("a[%d]=%d,",i,L.data[i]);
printf("\n");
IncreaseSize(L,3);
for(int i=0;i<L.MaxSize;i++) printf("a[%d]=%d,",i,L.data[i]);
return 0;
}
3)顺序表的四大特点
可以随机访问。
存储密度高,只需存数据,不需存指针。
扩展容量不方便,静态分配无法扩展,动态分配需要把存储的内容复制到新分配的区域。
插入、删除效率低。
4)顺序表的基本操作
插入
#include<stdio.h>
#include<stdlib.h>
#define InitSize 10
typedef struct{
int * data;
int length;
int MaxSize;
}SeqList;
void InitList(SeqList &L)
{
int i=0;
L.data=(int *)malloc(InitSize*sizeof(int));
for(i=0;i<L.MaxSize;i++)L.data[i]=0;
L.length=0;
L.MaxSize=InitSize;
}
bool ListInsert(SeqList &L,int i,int e)//i是位序,不是数组下标
{
if(i<1||i>L.length+1) return false;
if(i>L.MaxSize) return false;
int j;
for(j=L.length;j>=i;j--)
L.data[j]=L.data[j-1]; //1
L.data[i-1]=e;
L.length++;
return true;
}
int main(void)
{
SeqList L;
InitList(L);
ListInsert(L,1,1);
ListInsert(L,2,2);
ListInsert(L,3,3);
for(int i=0;i<L.length;i++)printf("data[%d]=%d,",i,L.data[i]);
printf("\n");
ListInsert(L,1,0);
for(int i=0;i<L.length;i++)printf("data[%d]=%d,",i,L.data[i]);
return 0;
}
最优时间复杂度:O(1),即插入到表尾——位序n+1处,不需要执行最深层的for循环(1处);
最坏时间复杂度:O(n),插入到表头——位序为1处,最深层for循环执行n次;
平均复杂度:O(n),每个位序插入的可能性是1/(n+1),(0+1+…+n)/(n+1)=n*(n+1)/2 /(n+1)=n/2;
删除
#include <stdio.h>
#include <stdlib.h>
#define InitSize 10
typedef struct{
int * data;
int MaxSize;
int length;
}SeqList;
void InitList(SeqList &L)
{
int i;
L.data=(int *)malloc(InitSize*sizeof(int));
L.MaxSize=InitSize;
for(i=0;i<L.MaxSize;i++) L.data[i]=0;
L.length=0;
}
bool ListInsert(SeqList &L,int i,int e)
{
int j;
if(i<1||i>L.length+1)return false;
if(i>L.MaxSize)return false;
for(j=L.length;j>=i;j--)
L.data[j]=L.data[j-1];
L.data[i-1]=e;
L.length++;
return true;
}
//按位删除
bool ListDelete(SeqList &L,int i,int &e) //此处传入的参数i是位序为i的元素,即删除对应下标为i-1的元素
{
if(i<1||i>L.length)return false;
e=L.data[i-1];
int j;
for(j=i;j<L.length;j++)
L.data[j-1]=L.data[j];
L.length--;
return true;
}
int main(void)
{
int e=-1;
SeqList L;
InitList(L);
ListInsert(L,1,1);
ListInsert(L,2,2);
ListInsert(L,3,3);
for(int i=0;i<L.length;i++)printf("data[%d]=%d,",i,L.data[i]);
printf("\n");
if(ListDelete(L,1,e))
printf("Delete %d\n",e);
else
printf("Failure!\n");
for(int i=0;i<L.length;i++)printf("data[%d]=%d,",i,L.data[i]);
return 0;
}
最好时间复杂度:O(1);
最坏时间复杂度:O(n);
平均复杂度:O(n);
按位查找
#include<stdio.h>
# define MaxSize 10
typedef struct{
int data[MaxSize];
int length;
}SeqList;
void InitList(SeqList &L)
{
int i;
for(i=0;i<MaxSize;i++)L.data[i]=0;
L.length=0;
}
bool ListInsert(SeqList &L,int i,int e)
{
int j;
if(i<1||i>L.length+1)return false;
if(i>MaxSize)return false;
for(j=L.length;j>=i;j--)
L.data[j]=L.data[j-1];
L.data[i-1]=e;
L.length++;
return true;
}
int GetElem(SeqList L,int i){
if(i<1||i>L.length) return -1;
return L.data[i-1];
}
int main(void)
{
SeqList L;
InitList(L);
ListInsert(L,1,1);
ListInsert(L,2,2);
ListInsert(L,3,3);
printf("%d,",GetElem(L,1));
printf("%d\n",GetElem(L,0));
return 0;
}
时间复杂度:O(1)
按值查找
int LocateElem(SeqList L,int e)
{
int i;
for(i=0;i<L.length;i++)
if(L.data[i]==e)return i+1;
return 0;
}
最好时间复杂度:O(1)
最坏时间复杂度:O(n)
平均时间复杂度:O(n)
5)练习题
T1 删除顺序表中的最小值(假设唯一),并由函数返回被删元素的值。若顺序表为空则显示出错信息并退出运行
//删除最小元素,并返回该元素的值
bool ListDelete(SeqList &L,int &min)
{
int i;
int index=0;//记录最小元素的位序
if(L.length==0) return false; //如果链表为空,则返回false
min=L.data[0];
//查找最小的元素
for(i=1;i<L.length;i++)
{
if(L.data[i]<min) min=L.data[i];
index=i+1;
}
//将空出来的位置由最小的元素代替
L.data[index]=L.data[L.length-1];
L.length--;
return true;
}
T2 设计算法将顺序表中的元素逆序,要求算法的空间复杂度O(1);
bool ReverseList(SeqList &L)
{
if(L.length==0)return false;
int i;
int j;
int temp;
for(i=0;i<(L.length)/2;i++)
{
j=L.length-i-1;
temp=L.data[i];
L.data[i]=L.data[j];
L.data[j]=temp;
}
return true;
}
*T3 长度为n的顺序表L,编写一个时间复杂度为O(n),空间复杂度为O(1)的算法。该算法删除线性表中所有值为x的元素。
void DeleteByValue(SeqList &L,int x)
{
int k=0;//用来存储不是x的数的个数
for(int i=0;i<L.length;i++)
{
if(L.data[i]!=x)
{
L.data[k]=L.data[i];
k++;
}
}
L.length=k;
}
T4 从有序顺序表中删除其值在给定值s与t之间(要求s<t)的所有元素,如果s或者t不合理或顺序表为空,则显示出错信息并退出运行。
bool DeleteBetweenValue(SeqList &L,int s,int t,int &start,int &end)
{
if(L.length==0) return false;
if(s>=t) return false;
int i;
for(i=0;i<L.length;i++)
{
if(L.data[i]==s)start=i+1;
if(L.data[i]==t)end=i+1;
}
return true;
}
int main(void)
{
int e=0;
SeqList L;
InitList(L);
int start;
int end;
ListInsert(L,1,1);
ListInsert(L,2,2);
ListInsert(L,3,3);
for(int i=0;i<L.length;i++)printf("%d,",L.data[i]);
DeleteBetweenValue(L,1,2,start,end);
printf("\n");
int k=end-start+1;
for(int j=0;j<k;j++) ListDelete(L,start,e);
for(int i=0;i<L.length;i++)printf("%d,",L.data[i]);
return 0;
}
3.单链表
1)顺序表与链式表的比较
顺序表:存储密度高、可以随机存取,但是扩展容量不方便,删除、插入元素开销大。
链式表:需要存储指针信息,不能随机存取,只能依次遍历,删除、插入元素开销小,扩展容量很容易。
2)单链表定义
不带头结点
#include <stdio.h>
typedef struct LNode{
int data;
struct LNode * next;
}LNode,*LinkList;
//无头节点
bool InitList(LinkList &L)
{
L=NULL; //把头指针置空
return true;
}
//判断链表是否为空,如果为空,头指针指向NULL
bool IsListEmpty(LinkList L)
{
return (L==NULL);
}
int main(void)
{
LinkList L; //声明一个指向单链表的指针
InitList(L);
printf("%d",IsListEmpty(L));
return 0;
}
带头结点
#include <stdio.h>
#include <stdlib.h>
typedef struct LNode{
int data;
struct LNode * next;
}LNode,*LinkList;
//带头节点
bool InitList(LinkList &L)
{
L=(LNode *)malloc(sizeof(LNode));
if(L==NULL)return false; //上面头指针指向了头结点,头指针还指向NULL,说明内存空间不足,返回false;
L->next=NULL; //头结点置空
return true;
}
//判断链表是否为空,如果为空,头指针指向NULL
bool IsListEmpty(LinkList L)
{
return (L->next==NULL);
}
int main(void)
{
LinkList L; //声明一个指向单链表的指针
InitList(L);
printf("%d",IsListEmpty(L));
return 0;
}
3)单链表的插入
按序号插入——带头结点(推荐)
#include <stdio.h>
#include <stdlib.h>
typedef struct LNode{
int data;
LNode * next;
}LNode,*LinkList;
void InitList(LinkList &L)
{
L=(LNode *)malloc(sizeof(LNode)); //分配一个空间作为头结点,头指针指向头结点
L->next=NULL; //头节点的next域置空
}
//按位序插入
bool ListInsert(LinkList &L,int i,int e)
{
if(i<1) return false;
LNode * p=L; //指针p指向头结点
int j=0; //下标j用来记录指针p指向第几个结点
while(p!=NULL&&j<i-1) //循环找到第i-1个结点
{
p=p->next;
j++;
}
if(p==NULL) return false; //i值超过了链表的范围,不合法
LNode * s=(LNode *)malloc(sizeof(LNode)); //指针s指向一个新的结点
s->data=e; //将要插入的数据e存到结点中
s->next=p->next;
p->next=s;
return true;
}
int main(void)
{
LinkList L; //申请一个LinkList类型的头指针
InitList(L);
if(ListInsert(L,1,1))
{
printf("%d\n",L->next->data);
}
else
printf("Failed!\n");
return 0;
}
最好时间复杂度:O(1),最坏时间复杂度:O(n),平均时间复杂度:O(n)
按序号插入——不带头结点
#include <stdio.h>
#include <stdlib.h>
typedef struct LNode
{
int data;
LNode * next;
}LNode,*LinkList;
//不带头结点
bool InitList(LinkList &L)
{
L=NULL; //将头指针置空
return true;
}
//按位插入
bool ListInsert(LinkList &L,int i,int e)
{
if(i<1) return false; //非法输入
if(i=1)
{
LNode * s=(LNode *)malloc(sizeof(LNode));
s->data=e;
s->next=L;
L=s;
return true;
}
int j=1; //用来记录p指向第几个结点
//找到第i-1个结点
LNode * p=L;
while(p!=NULL&&j<i-1)
{
p=p->next;
j++;
}
if(p==NULL) return false;
LNode * s=(LNode *)malloc(sizeof(LNode));
s->data=e;
s->next=p->next;
p->next=s;
return true;
}
int main(void)
{
LinkList L;//声明一个头指针
InitList(L);
if(ListInsert(L,0,1))
{
printf("%d\n",L->data);
}
else
printf("Failed!\n");
return 0;
}
指定结点后插操作
bool InsertNextNode(LNode * p,int e)
{
if(p==NULL) return false; //i值超过了链表的范围,不合法
LNode * s=(LNode *)malloc(sizeof(LNode)); //指针s指向一个新的结点
s->data=e; //将要插入的数据e存到结点中
s->next=p->next;
p->next=s;
return true;
}
时间复杂度:O(1)
指定结点前插操作
#include <stdio.h>
#include <stdlib.h>
typedef struct LNode
{
int data;
LNode * next;
}LNode,*LinkList;
void InitList(LinkList &L)
{
LNode * p;
L=(LNode *)malloc(sizeof(LNode));
p=L;
p->next=NULL;
}
//按位插入
bool ListInsert(LinkList &L,int i,int e)
{
if(i<1) return false; //非法输入
int j=0; //用来记录p指向第几个结点
//找到第i-1个结点
LNode * p=L;
while(p!=NULL&&j<i-1)
{
p=p->next;
j++;
}
if(p==NULL) return false;
LNode * s=(LNode *)malloc(sizeof(LNode));
s->data=e;
s->next=p->next;
p->next=s;
return true;
}
//核心代码块,插入制定结点之前
bool InsertPrioList(LNode * p,int e)
{
if(p==NULL)return false;
LNode * s=(LNode *)malloc(sizeof(LNode));
if(s==NULL)return false;
//把s插入p结点之后
s->next=p->next;
p->next=s;
//把p的数据拷贝到s,再把要插入的数据存入p
s->data=p->data;
p->data=e;
return true;
}
int main(void)
{
LinkList L;
InitList(L);
ListInsert(L,1,1);
LNode * r=L->next;
InsertPrioList(r,5);
printf("%d",L->next->data);
return 0;
}
时间复杂度:O(1)
4)单链表的删除
#include <stdio.h>
#include <stdlib.h>
typedef struct LNode
{
int data;
LNode * next;
}LNode,*LinkList;
bool InitList(LinkList &L)
{
L=(LNode *)malloc(sizeof(LNode));
if(L==NULL)return false;
L->next=NULL;
return true;
}
//按位插入
bool ListInsert(LinkList &L,int i,int e)
{
if(i<1) return false; //非法输入
int j=0; //用来记录p指向第几个结点
//找到第i-1个结点
LNode * p=L;
while(p!=NULL&&j<i-1)
{
p=p->next;
j++;
}
if(p==NULL) return false;
LNode * s=(LNode *)malloc(sizeof(LNode));
s->data=e;
s->next=p->next;
p->next=s;
return true;
}
//核心代码,按位序删除
bool ListDelete(LinkList &L,int i,int &e)
{
if(L->next==NULL)return false;
if(i<1)return false;
//找到第i-1个位置
LNode * p=L;
int j=0;
while(j<i-1&&p!=NULL)
{
p=p->next;
j++;
}
if(p==NULL) return false;
//在第i-1个位置后执行删除操作
LNode * q=p->next;
e=q->data;
p->next=q->next;
free(q);
return true;
}
void PrintList(LinkList L)
{
LNode * p=L;
while(p->next!=NULL)
{
p=p->next;
printf("%d ",p->data);
}
printf("\n");
}
int main(void)
{
LinkList L;
InitList(L);
ListInsert(L,1,1);
ListInsert(L,2,2);
ListInsert(L,3,3);
LNode * p=L;
PrintList(L);
int e;
ListDelete(L,2,e);
p=L;
PrintList(L);
return 0;
}
最好时间复杂度:O(1)、最坏、平均时间复杂度O(n).
指定结点的删除
bool DeleteNode(LNode * p)
{
if(p==NULL) return false;
LNode * q=p->next;
p->data=q->data;
p->next=q->next;
free(q);
return true;
}
时间复杂度:O(1)
[注]若要删除最后一个结点,p->next为NULL,故q不存在data域,这种解法不适用了。
5)单链表的查找
按位查找
LNode * GetElem(LinkList L,int i)
{
if(i<0) return NULL;
LNode * p=L;
int j=0;
while(j<i&&p!=NULL)
{
p=p->next;
j++;
}
return p;
}
平均时间复杂度:O(n)
按值查找
LNode * GetElem(LinkList L,int i)
{
if(i<0) return NULL;
LNode * p=L;
int j=0;
while(j<i&&p!=NULL)
{
p=p->next;
j++;
}
return p;
}
表长
int ListLength(LinkList L)
{
int i=0;
LNode * p=L->next;
while(p!=NULL)
{
p=p->next;
i++;
}
return i;
}
6)单链表的建立
尾插法
可以通过每一次插入都调用
while 循环
{
//取数据元素
ListInsert(LinkList,length+1,e); //插至链表尾部
length++;
}
但每次插入一个元素都要从头开始遍历,时间复杂度为O(n^2);可以通过一个指针来指向表尾,每次移动尾指针改良;
LinkList Link_TailInsert(LinkList &L) //正向建立单链表
{
//初始化
L=(LNode *)malloc(sizeof(LNode)); //头指针指向头结点
LNode * p=L; //指针p指向头结点
//用户输入数据、并依次生成结点,直至用户输入9999退出
int x;
scanf("%d",&x);
while(x!=9999)
{
LNode * q=(LNode *)malloc(sizeof(LNode));
p->next=q;
p=q;
p->data=x;
scanf("%d",&x);
}
p->next=NULL; //尾指针置空,不做这一步,最后尾指针会指向自己
return L;
}
头插法
LinkList Link_HeadInsert(LinkList &L)
{
int a;
LNode * p;
//初始化
L=(LNode *)malloc(sizeof(LNode)); //头指针指向头结点
L->next=NULL; //必须指向NULL
//输入数据、并把输入的数据插入到头结点之后
scanf("%d",&a);
while(a!=9999)
{
p=(LNode *)malloc(sizeof(LNode));
p->data=a;
p->next=L->next;
L->next=p;
scanf("%d",&a);
}
return L;
}
头插法的应用:实现链表的逆置
//法一:通过新建一个链表实现
LinkList ReverseList(LinkList &L)
{
LNode * p; //指针
LinkList s=(LNode *)malloc(sizeof(LNode)); //生成一个新的链表
s->next=NULL;
while(L->next!=NULL)
{
//从头开始依次取下链表L的结点
p=L->next; //指向要被取下的结点
L->next=p->next;
//把取下的数据使用头插法插入到新的链表s
p->next=s->next;
s->next=p;
}
return s;
}
//法二:就地转置,将头结点摘下,然后从第一结点开始,依次前插入到头结点的后面(头插法),直到最后一个结点为止。
LinkList ReverseList2(LinkList &L)
{
LNode *p,*q; //p指针指向位序为一的结点
p=L->next;
L->next=NULL; //取下头结点
//使用头插法把结点从头依次插入头结点后面
while(p!=NULL)
{
q=p->next;
p->next=L->next;
L->next=p;
p=q;
}
return L;
}
其实以上两个方法基本上原理是相同的,用法二更好,因为是原地转置,用法一也可以再最后把头指针L->s;
4.双链表
1)双链表的定义
typedef struct DNode{
int data;
DNode * next,* prior;
}DNode,*DLinkList;
bool InitList(DLinkList &L)
{
L=(DNode *)malloc(sizeof(DNode));
if(L==NULL)
return false;
L->next=NULL;
L->prior=NULL;
return true;
}
bool Empty(DLinkList &L)
{
return(L->next==NULL);
}
2)双链表的插入
bool ListInsert(DNode * p,DNode * s)
{
if(p==NULL||s==NULL) return false;
s->next=p->next; //1
if(p->next!=NULL)
p->next->prior=s; //2
s->prior=p; //3
p->next=s; //4 1,2必须在4前
return true;
}
这是指定结点的后插操作,按位序插入只需要设置尾指针,依次进行后插操作即可;前插操作只需要找到一个结点的前继结点,对其进行后插操作即可。
3)双链表的删除
bool ListDelete(DNode * p) //删除指定结点的后继节点
{
if(p==NULL) return false;
DNode * q=p->next;
if(q==NULL) return false;
p->next=q->next;
if(q->next!=NULL) q->next->prior=p;
free(q);
return true;
}
销毁一个双链表
void DestroyList(DLinkList &L)
{
while(L->next!=NULL) ListDelete(L);
free(L); //释放头结点
L->next=NULL; //头指针置空
}
4)双链表的遍历
//后向遍历
while(p!=NULL)
{
//...
p=p->next;
}
//前向遍历
while(p->pror!=NULL) //跳过了头结点
{
//...
p=p->prior;
}
5.循环链表
1)循环单链表
typedef struct LNode
{
int data;
LNode * next;
}LNode,*LinkList;
bool InitList(LinkList L)
{
L=(LNode *)malloc(sizeof(LNode));
if(L=NULL) return false;
L->next=L;
return true;
}
bool Empty(LinkList L)
{
if(L->next=L) return true;
return false;
}
bool isTail(LinkList L,LNode * p) //判断一个结点是否为尾节点
{
return(p->next==L);
}
使用循环单链表时,如果经常需要对表头、表尾操作,可以使L指向表尾,这样找到表头、表尾的时间复杂度都是O(1)。
2)循环双链表
typedef struct LNode
{
int data;
LNode * next;
LNode * prior;
}LNode,*LinkList;
bool InitList(LinkList L)
{
L=(LNode *)malloc(sizeof(LNode));
if(L=NULL) return false;
L->next=L;
L->prior=L;
return true;
}
bool Empty(LinkList L)
{
if(L->next=L) return true;
return false;
}
bool isTail(LinkList L,LNode * p) //判断一个结点是否为尾节点
{
return(p->next==L);
}
bool ListInsert(DNode * p,DNode * s)
{
if(p==NULL||s==NULL) return false;
s->next=p->next; //1
p->next->prior=s; //2 尾节点会指向头结点,不会置空,因此不需要条件判断
s->prior=p; //3
p->next=s; //4 1,2必须在4前
return true;
}
bool ListDelete(DNode * p) //删除指定结点的后继节点
{
if(p==NULL) return false;
DNode * q=p->next;
p->next=q->next;
q->next->prior=p;
free(q);
return true;
}
指针类链表小结
链表类代码在编写时需要考虑三个问题:
1.如何判空,知道空的状态如何判断就知道如何初始化
2.如何判断表头,表尾,知道怎么判断表头表尾就知道怎么遍历,就知道了怎么去查找、删除、增加
3.表尾、表头是否需要特殊处理
6.静态链表
1)静态链表的定义
#define MaxSize 10
typedef struct Node
{
int data;
int next;
}SLinkList[MaxSize];
void InitList()
{
SLinkList a;
}
2)静态链表的基本操作
初始化:要将a[0]置为-1;将其他空的结点游标置为-2(方便后续计算机判断哪些结点是空结点);
查找:通过游标依次遍历元素,时间复杂度为O(n)
插入:
1.找到一个空的结点用来存放元素
2.找到位序为i-1的结点
3.修改新的结点的next游标
4.修改i-1结点的next
删除:
1.找到其前驱结点
2.修改前驱结点游标
3.被删除结点的游标置为-2;
3)静态链表的优缺点
优点:增删容易,不需要大量的移动数据
缺点:查找需要遍历,容量固定
7.顺序表与链表的对比
顺序表 | 链式表 | |
逻辑结构 | 均为线性表; | 均为线性表; |
存储结构 | 1)基于静态分配的顺序表不能改变其大小,基于动态分配的顺序表改变容量不容易 。分配一片连续的存储区域不方便。2)存储密度较高。 | 1)结点无需申请一大片连续的存储空间,可以改变容量的大小。2)存储密度较低。 |
基本操作 | 1)初始化容量大小不好确定。2)静态分配销毁由系统自动完成,动态分配手动销毁。3)插入、删除操作时间复杂度较大。4)可以随机访问元素,按位查找时间复杂度为O(1),按值查找时间复杂度为O(n)【无序】或者O(log2n)【有序】 | 1)销毁操作需要手动free。2)优点是插入、删除结点很方便 ,只需要改变next指针所指向的元素。3)不能够随机访问元素,只能够通过遍历的方式进行元素的访问。查找的时间复杂度为O(n) |
注:虽然顺序表、链式表的插入、删除操作时间复杂度都为O(n),但顺序表的时间开销来自于元素的移动,
链式的时间开销来自于结点遍历,故顺序表的时间花销其实更大。
8.练习题
T1 设计一个递归算法,删除不带头结点的单链表L中所有值为x的结点
//设计一个递归算法,删除不带头结点的单链表L中所有值为x的结点
void Delete_x(LinkList &L,int x)
{
LNode * p;
if(L==NULL)return;
while(L->data!=x)
{
Delete_x(L->next,x);
return;
}
p=L;
L=L->next;
free(p);
Delete_x(L,x);
}
T21带头结点单链表,头指针为list。在不改变链表的前提下,设计一个尽可能高效的算法,查找链表中倒数第k个位置上的结点(k为正整数)。若查找成功,算法输出该结点的data域的值,并返回1;否则,只返回0。要求:
1)描述算法的基本设计思想
2)描述算法的详细实现步骤
3)根据设计思想和步骤,采用程序设计语言描述算法,关键之处请给出简要注释。
解:
1)两个指针p,q分别指向位序为1的结点(头结点的后继结点),指针p依次移动直至指向位序为k的元素时,指针q随p同步移动,当指针p移动至表尾,指针q所指向元素即为倒数第k个元素。
2)算法的详细实现步骤如下:
//1.count=0,p,q指向链表头结点的后继结点;
//2.若p为空,返回0;
//3.若count<k,count累加,若count=k,指针q后移;
//4.p结点后移,
//5.若count等于k,则查找成功,输出该结点的data域,返回1;
//6.算法结束
3、栈和队列
1.栈
1)栈的基本概念
只能从栈顶进行插入或者删除操作的线性表
2)栈的基本操作
InitStack(&S);
DestroyStack(&L);
Push(&S,x);
Pop(&S,&x);
GetTop(S,&x);
StackEmpty(S);
3)顺序栈的实现
#include <stdio.h>
#define MaxSize 10
typedef struct
{
int data[MaxSize];
int top;
}SeqStack;
//初始化
void InitStack(SeqStack &S)
{
S.top=-1;
}
//栈空判断
bool StackEmpty(SeqStack S)
{
return(S.top==-1);
}
//入栈
bool Push(SeqStack &S,int x)
{
if(S.top==MaxSize-1) return false;
S.data[++S.top]=x;
return true;
}
//出栈
bool Pop(SeqStack &S,int &x)
{
if(S.top==-1) return false;
x=S.data[S.top--];
return true;
}
//读取栈顶元素
bool GetTop(SeqStack S,int &x)
{
if(S.top==-1) return false;
x=S.data[S.top];
return true;
}
//上述代码top指针初始指向栈顶
int main()
{
SeqStack S;
InitStack(S);
printf("%d\n",StackEmpty(S));
Push(S,2);
printf("%d\n",StackEmpty(S));
int x;
Pop(S,x);
printf("%d,%d\n",StackEmpty(S),x);
Push(S,3);
GetTop(S,x);
printf("%d,%d\n",StackEmpty(S),x);
return 0;
}
缺点:存储容量不可改变
4)链式栈
#include <stdio.h>
#include <stdlib.h>
//定义栈结点
typedef struct SNode{
int data;
SNode * next;
}*LinkStack,SNode;
//初始化栈(不带头结点)
bool InitStack(LinkStack &S)
{
S=NULL;
return true;
}
//判空
bool StackEmpty(LinkStack S)
{
return (S==NULL);
}
//入栈操作
bool Push(LinkStack &S,int x)
{
//在头部插入元素
SNode * p=(SNode *)malloc(sizeof(SNode));
//这里并不需要区分是不是第一个结点,两者操作时一致的!!!
p->data=x;
p->next=S;
S=p;
return true;
}
//出栈操作(不带头结点)
bool Pop(LinkStack &S,int &x)
{
if(S==NULL) return false;
SNode * p=S;
x=S->data;
S=S->next;
free(p);
return true;
}
//打印栈中元素(不带头结点)
void PrintStack(LinkStack S)
{
if(S==NULL) return;
while(S!=NULL)
{
printf("%d ",S->data);
S=S->next;
}
printf("\n");
}
//对于链式存储结构实现的栈(不带头结点)进行增删改查操作
int main(void)
{
int x;
LinkStack S;
InitStack(S);
printf("%d\n",StackEmpty(S));
Push(S,1);
Push(S,2);
Push(S,5);
Push(S,1);
PrintStack(S);
Pop(S,x);
Pop(S,x);
PrintStack(S);
return 0;
}
2.队列
1)队列的基本概念
队头:允许删除元素的一端;
队尾:允许插入元素的一端。
2)队列的顺序存储结构
#include <stdio.h>
#define MaxSize 10
typedef struct SeQueue
{
int data[MaxSize];
int front,rear;
};
//初始化队列,队头、队尾指针均指0
void InitQueue(SeQueue &Q){
Q.front=0;
Q.rear=0;
}
//队列判空:队头与队尾指向相同空间,即差值为0,队空
bool QueueEmpty(SeQueue Q)
{
return (Q.front==Q.rear);
}
//入队操作:先将数据插入至队尾,再将尾指针后移
bool EnQueue(SeQueue &Q,int x)
{
if((Q.rear+1)%MaxSize==Q.front) return false; //判断队列是否满,满则返回false,这里牺牲了一个存储空间用来区分队满和队空
Q.data[Q.rear]=x;
Q.rear=(Q.rear+1)%MaxSize; //这里要注意!由于这种方式实现的队列在逻辑上似乎是循环的,也称作循环队列
return true;
}
//出队操作:先将数据取出,再将头指针后移
bool DeQueue(SeQueue &Q,int &x)
{
if(Q.front==Q.rear) return false; //判断是否队空
x=Q.data[Q.front];
Q.front=(Q.front+1)%MaxSize;
return true;
}
//查;获取队头元素
bool GetHead(SeQueue Q,int &data)
{
if(Q.front==Q.rear) return false;
data=Q.data[Q.front];
return true;
}
int main(void)
{
SeQueue Q;
int x;
int data;
InitQueue(Q);
printf("队列是否为空:%d\n",QueueEmpty(Q));
EnQueue(Q,1);
printf("队列是否为空:%d\n",QueueEmpty(Q));
GetHead(Q,data);
printf("队头元素为:%d\n",data);
DeQueue(Q,x);
printf("队列是否为空:%d\n",QueueEmpty(Q));
return 0;
}
队列元素个数:(Q.rear-Q.front+MaxSize)%MaxSize;
如果想要不浪费一个存储空间用来区分队满与队空,我们可以使用一个在队列中定义一个size用来记录元素个数,初始设为0,增加元素size++,减少元素size–,队满条件size= =Maxsize,队空条件为:size= =0;
也可以设置tag,删除tag置为0,插入tag置为1,队满一定是因为插入,tag一定为1;
3)队列的链式存储结构
#include <stdio.h>
#include <stdlib.h>
//定义链式队列结点
typedef struct LinkNode
{
int data;
LinkNode * next;
}LinkNode;
//定义链式队列
typedef struct
{
LinkNode * front,* rear;
}LinkQueue;
//初始化(带头结点)
void InitQueue(LinkQueue &Q)
{
Q.front=Q.rear=(LinkNode *)malloc(sizeof(LinkNode));
Q.front->next=NULL;
}
//队列判空:如果头、尾指针指向同一个结点或者头结点的next指向为NULL则为空
bool QueueEmpty(LinkQueue Q)
{
return (Q.front->next==NULL);
//return (Q.front=Q.rear);
}
//入队操作(带头结点)
void EnQueue(LinkQueue &Q,int x)
{
//申请一个新的结点用于存储待插入的数据
LinkNode * p=(LinkNode *)malloc(sizeof(LinkNode));
p->data=x;
//将新结点入队,即在队尾插入新结点
p->next=NULL;
Q.rear->next=p;
Q.rear=p;
}
//出队操作(带头结点)
void DeQueue(LinkQueue &Q,int &x)
{
LinkNode * p;
p=Q.front->next;
x=p->data;
Q.front->next=p->next;
//!!如果删除的元素为表尾结点,要将尾指针指向头结点
if(Q.rear==p)Q.rear=Q.front;
free(p);
}
//带头结点版本
int main(void)
{
int x;
LinkQueue Q;
InitQueue(Q);
printf("队列是否为空:%d\n",QueueEmpty(Q));
EnQueue(Q,2);
printf("队列是否为空:%d\n",QueueEmpty(Q));
DeQueue(Q,x);
printf("删除的元素为:%d\n",x);
printf("队列是否为空:%d\n",QueueEmpty(Q));
return 0;
}
#include <stdio.h>
#include <stdlib.h>
//定义链式队列结点
typedef struct LinkNode
{
int data;
LinkNode * next;
}LinkNode;
//定义链式队列
typedef struct
{
LinkNode * front,* rear;
}LinkQueue;
//初始化队列(不带头结点):使front、rear都指向NULL
void InitQueue1(LinkQueue &Q)
{
Q.front=NULL;
Q.rear=NULL;
}
//判断队列是否为空(不带头结点)
bool QueueEmpty1(LinkQueue Q)
{
return (Q.front==NULL);
}
//入队操作(不带头结点)
void EnQueue1(LinkQueue &Q,int x)
{
//申请一个结点用来存储入队数据
LinkNode * p=(LinkNode *)malloc(sizeof(LinkNode));
p->data=x;
//执行入队操作
p->next=NULL;
if(Q.rear==NULL)
{
Q.front=p;
Q.rear=p;
}
else
{
Q.rear->next=p;
Q.rear=p;
}
}
//出队操作(不带头结点)
void DeQueue1(LinkQueue &Q,int &x)
{
LinkNode * p=Q.front;
x=p->data;
Q.front=p->next;
//如果出队恰好使最后一个结点,那么要把rear恢复为NULL
if(Q.rear==p)
Q.rear=NULL;
free(p);
}
int main(void)
{
int x;
LinkQueue Q;
InitQueue1(Q);
printf("队列是否为空:%d\n",QueueEmpty1(Q));
EnQueue1(Q,2);
printf("队列是否为空:%d\n",QueueEmpty1(Q));
DeQueue1(Q,x);
printf("删除的元素为:%d\n",x);
printf("队列是否为空:%d\n",QueueEmpty1(Q));
return 0;
}
3.栈和队列的应用
1)栈在括号匹配中的应用
((())),最后的左括号要优先匹配——栈
#include <stdio.h>
#define MaxSize 10
typedef struct
{
char data[MaxSize];
int top;
}SeqStack;
//初始化栈
void InitStack(SeqStack &S)
{
for(int i=0;i<MaxSize;i++)
{
S.data[i]=NULL;
}
S.top=-1; //指栈顶元素,初始时置为-1;
}
//判断栈是否为空
bool StackEmpty(SeqStack S)
{
return S.top==-1;
}
//入栈
bool Push(SeqStack &S,char x)
{
if(S.top==MaxSize-1) return false;
S.top++;
S.data[S.top]=x;
return true;
}
//出栈
bool Pop(SeqStack &S,char &x)
{
if(S.top==-1) return false;
x=S.data[S.top];
S.top--;
return true;
}
//核心代码,这里为了结果直观,对不同匹配情况进行了打印,考试时可以不用,在调用Push、Pop等时对其简单
//注 释,可直接调用
bool BraketCheck(char Str[],SeqStack S)
{
//判断是否有括号1
for(int i=0;i<6;i++)
{
//判断是否有左括号
if(Str[i]=='['||Str[i]=='(')
{
//左括号入栈
Push(S,Str[i]);
}
else
{
//判断是否栈空
if(!StackEmpty(S))
{
char b;
Pop(S,b); //弹出栈顶元素b
//判断括号是否匹配,
if(Str[i]==')'&&b=='(') { printf("%c %c\n",b,Str[i]);}
else if(Str[i]==']'&&b=='['){ printf("%c %c\n",b,Str[i]);}
else
{
printf("Failed!Different type of baracet!\n");return false;
}
}
else
{
printf("Failed!Right Sigle.\n"); return false;
}
}
}
if(StackEmpty(S)){printf("Sucess!\n"); return true;}
else{printf("Failed!Left Sigle!\n");return true;}
}
//栈的应用——括号的匹配
int main(void)
{
SeqStack S;
InitStack(S);
char Str[6]={'[','(',')',']',')',')'};
BraketCheck(Str,S);
return 0;
}
2)栈在表达式中的应用
后缀表达式的求值:
将后缀表达式依次压栈,遇到操作符号则弹出栈顶的两个元素,将其进行运算,先出栈的放在操作符右边;将运算的结果压栈。
//算法思想:设置操作数栈OPND,运算符栈OPTR,依次扫描表达式,遇到操作数则进操作数栈,遇到运算符则栈顶元素与运算符栈比较,若栈顶元素优先级低,则进栈,若栈顶元素优先级高则出栈。特别的,界限符'(运算优先级级最高直接入栈,)'则依次弹出运算符栈的元素,直到遇到‘(’,同时弹出操作数栈顶两个元素,第一个元素在运算符后,第二个元素在运算符前进行运算,将运算的结果重新压入操作数栈。
OprandType EvaluateExpression(){
InitStack(OPND); InitStack(OPTR);
PUSH(OPTR,'#');
ch=getchar();
while(GetTop(OPTR)!='#'||ch!='#'){
if(!In(ch,OP)) Push(OPND,ch);//不是运算符则进栈
else
switch(Precede(GetTop(OPTR),ch)){ //拿栈顶元素与其进行比较
case '<' :
Push(OPTR,ch);ch=getchar();
break;
case '=': //(,)中间没有其他运算符,脱括号
Pop(OPTR,x); ch=getchar();
break;
case '>':
Pop(OPTR,theta);
pop(OPND,b);pop(OPND,a);
Push(OPND,Opread(a,theta,b));
break;
} //switch
} //while
} //EvalueateExpression
3)进制转换
将十进制数N转换为8进制
//算法思想:除8、取余倒排
void convention(int n){
//初始化栈
InitStack(S);
//N除以8,取余数,余数入栈,n更新为商,直到商为0
while(n>0){
Push(S,n%8);
n=n/8;
}
for(!StackEmpty(S)){
Pop(S,e);
printf("%d",e);
}
}
4.串
1)串的基本操作
#include <stdio.h>
#define MaxSize 255
typedef struct
{
char ch[MaxSize];
int len;
}MyString;
//从指定位序开始截取串的子串
bool SubString(MyString &Sub,MyString S,int pos,int len)
{
if(pos+len-1>S.len) return false; //子串范围越界
for(int i=pos;i<pos+len;i++)
{
Sub.ch[i-pos+1]=S.ch[i];
}
Sub.len=len;
return true;
}
//比较两个串的大小
int CompareString(MyString S1,MyString S2)
{
//逐个字符比较
for(int i=1;i<=S1.len&&i<=S2.len;i++)
{
if(S1.ch[i]!=S2.ch[i]) return S1.ch[i]-S2.ch[i];
}
//扫描过的字符皆相同,比较两字符串长度
return S1.len-S2.len;
}
//定位子串
int indexString(MyString S,MyString Sub)
{
//暂存截取串
int i=1;
MyString temp;
temp.len=Sub.len;
while(i<=S.len-Sub.len+1)
{
SubString(temp,S,i,Sub.len);
if(CompareString(Sub,temp)!=0) ++i;
else return i;
}
return 0;
}
//串的基本操作
int main(void)
{
MyString S1;
S1.ch[0]=NULL;
S1.ch[1]='w';
S1.ch[2]='z';
S1.len=2;
MyString S2;
S2.ch[0]=NULL;
S2.ch[1]='z';
S2.len=1;
printf("%d\n",indexString(S1,S2));
return 0;
}
2)串的朴素模式匹配算法
//串的朴素模式匹配算法
int Index(MyString S,MyString Sub)
{
int i,j,k;//k用于定位S当前匹配到哪个位置,i,j分别用于S与Sub的活动定位
k=1;
i=k;
j=1;
while(S.len-k+1<=Sub.len)
{
if(S.ch[i]==Sub.ch[j])
{
++i;
++j;
}
else
{
j=1;
++k;
i=k;
}
if(j>Sub.len) return k;
else return 0;
}
}
4、树
1.二叉树
1)树的顺序存储
#include <stdio.h>
#define MaxSize 100
struct TreeNode{
int value;
bool isEmpty;
};
void InitTreeNode(TreeNode t[MaxSize])
{
for(int i=0;i<MaxSize;i++){
t[i].isEmpty=true;
}
}
void main(){
TreeNode t[MaxSize];
InitTreeNode(t);
}
2)树的链式存储
typedef struct BiTreeNode{
int value;
BiTreeNode * rchild,* lchild;
}BiTreeNode,* BiTree;
//插入根节点
BiTree root=(BiTree)malloc(sizeof(BiTreeNode));
root->value=1;
root->lchild=NULL;
root->rchild=NULL;
return true;
//插入新节点
BiTreeNode * p=(BiTreeNode *)malloc(sizeof(BiTreeNode));
p->value=2;
p->lchild=NULL;
p->rchild=NULL;
root->lchild=p;
return true;
//先序遍历
void PreOrder(BiTree T)
{
if(T!=NULL)
{
Visit(T);
PreOrder(T->lchild);
PreOrder(T->rchild);
}
}
//求树的深度:后序的应用
int TreeDepth(BiTree T)
{
if(T==NULL) return 0;
int l=TreeDepth(T->lchild);
int r=TreeDepth(T->rchild);
return l>r?l+1:r+1;
}
//中序遍历(非递归实现)
bool InOrderTraverse(BiTree T)
{
BiTreeNode * p=T; InitStack(S); //初始化一个栈
while(p||!ISEmpty(S))
{
if(p){push(S,p);p=p->lchild;}
else
{
pop(S,q);
printf("%d,",q->data);
p=q->rchild;
}
}
return true;
}
3)二叉树的层次遍历
//层次遍历
void LevelOrder(BiTree T)
{
LinkQueue Q;
InitQueue(Q);
BiTree p; //用来存DeQueue后的树结点
EnQueue(Q,T);
while(!IsEmpty(Q))
{
DeQueue(Q,p);
Visit(p);
if(T->lchild!=NULL){EnQueue(Q,p->lchild);}
if(T->rchild!=NULL){EnQueue(Q,p->rchild);}
}
}
4)线索二叉树的构造
#include <stdio.h>
//定义线索二叉树结点
typedef struct ThreadNode
{
int data; //数据域存放二叉树数据
ThreadNode * lchild,*rchild; //左右孩子
int ltag,rtag; //左右孩子标志位
}ThreadNode,* ThreadTree;
ThreadNode * pre; //全局变量,当前结点的前驱结点
构造并中序线索化
//访问某一个树结点同时进行线索化
void Visit(ThreadNode * p)
{
if(p->lchild==NULL)
{
p->lchild=pre;
p->ltag=1;
}
if(pre!=NULl&&pre->rchild==NULL)
{
pre->rchild=p; //建立前驱结点的后继线索
pre->rtag=1;
}
pre=p; //把pre置为当前结点,当下一次调用visit时访问的pre就是当前结点(下一次遍历的前驱)
}
//二叉树中序遍历(并调用Visit进行线索化)
void InThread(ThreadTree T)
{
if(T!=NULL)
{
InThread(T->lchild);
Visit(T);
InThread(T->rchild);
}
}
void CreateInThread(ThreadTree T)
{
pre=NULL;
if(T!=NULL)
{
InThread(T);
if(pre->rchild==NULL) pre->rtag=1; //对最后一个结点进行特殊处理
}
}
构造并先序线索化(构造代码同上)
//二叉树先序遍历(并调用Visit进行线索化)
void InThread(ThreadTree T)
{
if(T!=NULL)
{
Visit(T);
if(T->LTag==0)InThread(T->lchild); //!!!注意此处的判断条件,因为先序遍历访问顺序是根左右,如果根线索化了,无论根有没有左孩子,lchild都有指向,因此必须加此判断条件,判断其到底有没有左孩子
InThread(T->rchild);
}
}
5)线索二叉树的遍历
中序遍历
//找到以p为根的子树中第一个被中序遍历的结点,即最左下的那个结点
ThreadNode * FirstNode(ThreadNode * p)
{
while(p->ltag==0) p=p->lchild;
return p;
}
//找到某个结点的后继节点
ThreadNode * NextNode(ThreadNode * p)
{
if(p->rtag==1) return p->rchild;
else if(p->rchild==0) return FirstNode(p->rchild); //返回右子树最左下的那个结点
}
void Inorder(ThreadTree * T)
{
for(ThreadNode * p=FirstNode(T);p!=NULL;p=NextNode(p)) //先找到第一个结点,然后不断的找其后继
visit(p);
}
逆向中序遍历:从最后一个结点开始不断遍历其前驱即可。
6)平衡二叉树
//平衡二叉树结点
typedef struct AVLNode
{
int ket; //数据域
int balance; //平衡因子
struct AVLNode * lchild,* rchild;
}* AV
//LL平衡旋转(右旋)
f->lchild=p->rchild;
p->rchild=f;
gf->lchild/rchild=p;
5、图
1.图的存储结构
1)数组表示法
//-----图的组(邻接矩阵)存储表示-----
#define INFINITY INT_MAX //最大值∞
#define MAX_VERTEX_NUM 20 //最大的顶点个数
typedef enum {DG,DN,UDG,UDN}GraphKind; //{有向图(Digraph),有向网(DiNet),无向图(UniDigraph),无向网}
typedef struct ArcCell{
VRType adj; //VRType是顶点关系类型。对无权图,用1或者0表示相邻与否;对带权图,则为权值类型。
InfoType *info; //该弧相关信息的指针
}ArcCell,AdjMatrix[MAX_VERTEX_NUM][MAX_VERTEX_NUM];
typedef struct{
VertexType vexs[MAX_VERTEX_NUM]; //顶点向量
AdjMatrix arcs; //邻接矩阵
int vexnum,arcnum; //图的当前顶点数与边数
GraphKind kind; //图的种类标志
}MGpraph
2)邻接表
//---------图的邻接表存储表示-----------
#define MAX_VERTEX_NUM 20
typedef struct ArcNode{
int adjvex; //该弧指向的顶点的位置
struct ArcNode *nextarc; //指向下一条弧的指针
InfoType *info; //该弧相关信息的指针
}ArcNode;
typedef struct VNode{
VertexType data; //顶点信息
ArcNode *firstarc; //指向的一条依附该顶点的弧的指针
}VNode,AdjList[MAX_VERTEX_NUM];
typedef struct{
AdjList vertices;
int vexnum,arcnum; //图的当前顶点数和弧数
int kind; //图的种类
}
3)十字链表法
十字路口有方向,十字链表法是专门针对有向图设计的,旨在解决有向图用邻接表法找不到入度的这个问题。顶点结点包含三个域,数据域,firstin和firstout域分别指向第一个入度边,第一个出度边;边结点有五个域,尾域头域分别表示该弧的弧尾和弧头,链域tlink指向弧尾相同的下一条弧,hlink指向弧头相同的下一条弧,这样弧头相同的弧就在一个链表上,弧尾相同的弧也在一个链表上,info域指向该弧的相关信息。
2.图的遍历
1)广度优先遍历
void BFSTraverse(Graph G,Status(*visit)(int v)){
//按广度优先搜索遍历非递归遍历图G,使用辅助队列和访问标志数组visited
for(v=0;v<G.vexnum;v++) visited[v]=FALSE;
InitQueue(Q);
for(v=0;v<G.vexnum;++v)
if(!visited[v]){
visited[v]=True;
visit(v);
EnQueue(Q,v);
while(!QueueEmpty(Q)){
DeQueue(Q,u);
for(w=FirstAdjVex(G,u);w>=0;w=NextAdjVex(G,u,w))
if(!visited[w]){
visited[w]=True;
visit(w);
EnQueue(Q,w);
} //if
} //while
} //if
} //BFSTraverse
2)深度优先遍历
void DFSTraverse(Graph G,Statues(* Visit)(int v)){
for(v=0;v<G.vexnum;++v) visited[v]=FALSE;
for(v=0;v<G.vexnum;++v){
if(!visited[v]) DFS(G,v);
}
}
void DFS(Graph G,int v){
if(!visited[v]) {
visit(G,v);
visted[v]=TRUE;
} //if
while(w=FirstAdjVetex(G,v);w>=0;w=NextAdjVetex(G,v,w)){
if(!visited[w]){
DFS(G,w);
} //if
} //while
} //DFSTraverse
3.最小生成树(prim算法)
//prim算法的思想:选择一个初始顶点,把它加入辅助数组,计算该顶点到其他顶点的代价,选择其中最小代价对应点加入辅助数组,扫描加入新的顶点有没有使其到各个顶点的代价变化,并更新代价数组;重复进行以上步骤,知道所有顶点都已经加入了辅助数组
//记录顶点集U到V-U的代价最小的边的辅助数组
struct{
VertexType adjvex;
VRType lowcost;
}closedge[MAX_VERTEX_NUM];
void MiniSpanTree_PRIM(MGraph G,VetexType u){
//用prim算法从第v个顶点出发构造网的最小生成树T,并输出最小生成树的各条边
k=LocateVex(G,u);
for(j=0;j<G.vexnum;j++) //辅助数组初始化
if(j!=k) closedge[j]={u,G.arcs[k][j].adj}; //{adjvex,lowcost}
closedge[k].lowcost=0; //初始,U={u}
for(i=1;i<G.vexnum;++i){ //选择其余G.vexnum-1个顶点
k=minimum(closedge); //求出T的下一个顶点:第k顶点
printf(closedge[k].adjvex,G.vexs[k]);
closedge[k].lowcost=0;
for(j=0;j<G.vexnum;++j)
if(G.arcs[k][j]).adj<closedge[j].lowcost)
closedge[j]={G.vexs[k],G.arcs[k][j].adj};
}
} //MiniSpanTree
4.最短路径
1)单源最短路径
BFS可用于求不带权的图的单源最短路径(课本上没有)
bool viisted[Max_VeTex_num];
void BFS_Min_Distance(Graph G,int v){
//BFS求无向图的单源最短路径
for(int i=0;i<G.vexNum;i++){
d[i]=Inifinate; //最短路径大小
path[i]=-1; //最短路径从哪里过来
}
d[v]=0; //起点到自己的距离置为0;
visited[v]=true;
EnQueue(Q,v);
while(!IsEmpty(Q)){
DeQueue(Q,v);
for(w=FirstNeighbor(G,v);w>=0;w=NextNeighbor(G,v,w)){
if(!visited[w]){
visited[w]=true;
EnQueue(Q,w);
d[w]=d[v]+1;
path[w]=u;
}
}
}
}
5.拓扑排序
Status TopologicalOrder(ALGraph G,Stack &T){
//有向网G采用邻接表存储结构,求各顶点事件最早发生时间ve(全局变量)
//T为拓扑序列顶点栈,S为零入度顶点栈
//若G无回路,则栈T返回G的一个拓扑序列,且函数值为OK,否则为ERROR
FindInDegree(G,indegree); //对各顶点求入度indegree[0...vernum-1]
InitStack(S); //建立并初始化零入度顶点栈
count=0;ve[0...G.vexnum-1]=0; //初始化
while(!StackEmpty(S)){
Pop(S,j); Push(T,j);++count; //j号顶点入T栈并计数
for(p=G.vertices[j].firstarc;p;p->nextarc){
k=p->adjvex; //对j号顶点的每个邻接点的入度减1
if(--indgree[k]==0) Push(S,k); //若入度减为0,则入栈
if(ve[j]+*(p->info)>ve[k]) ve[k]=ve[j]+*(p->info) ;
}
}
if(count<G.vexnum) return ERROR; //该有向图有回路
else retrun OK;
}
6、排序
1.插入排序
1)直接插入排序
//直接插入排序,进行增序排序
void InsertSort(SqlList &L){
//从第2个元素开始,进行i-1轮插入
for(int i=2;i<=L.length;i++){
//如果待插入元素与之前元素比较已经属于有序,无需操作,否则执行操作
if(L.r[i].key<L.r[i-1].key){
//复制待插入元素为哨兵
L.r[0]=L.r[i];
for(int j=i-1;L.r[0].key<L.r[j].key;j--) //比待插入元素大的都往后移
L.r[j+1]=L.r[j];
L.r[j]=L.r[0];
} //if
} //for
} //InsertSort
2)折半插入排序
void BInsertSort(SeqList &L)
{
//从第二个元素开始把待排序序列插入到有序序列
for(int i=2;i<=L.length;i++)
{
L.r[0]=L.r[i];
int low=1,high=i-1; //从第i-1个元素(有序序列的最后一个元素)开始往前找空
while(low<=high){ //当low=high,还执行一次,将mid与low,high指向相同位置,并因此会执行else语句,使high+1位置为待插入元素的位置
mid=(low+high)/2;
if(L.r[mid].key<L.r[0].key) low=mid+1;
else high=mid-1;
} //循环结束,high+1为插入位置
for(int j=i-1;j>high+1;--j) L.r[j+1]=L.r[j];
L.r[high+1]=L.r[0];
}
}
3)希尔排序
//一趟增量为dk的希尔排序
void ShellSort(SeqList &L,int dk){
for(i=dk+1;i<=L.length;i++){ //从dk+1个元素开始,把无序子序列的元素插入有序序列
if(L.r[i]<L.r[i-dk]){ //当第i个元素比他前面子序列的元素要小,则执行,否则已经有序,无需操作
L.r[0]=L.r[i]; //复制为哨兵
for(j=i-dk;j>0&&(L.r[0].key<L.r[j].key);j-=dk) //比L.r[i]大的元素后移空位置
L.r[j+dk]=L.r[j];
L.r[j]=L.r[0];
}
}
}
2.交换排序
1)冒泡排序
void BubbleSort(SeqList &L){
for(int i=0;i<L.length;i++){
flag=false; //用来记录一趟冒泡排序是否发生了交换
for(int j=L.length;j>i;j--){
if(L.r[j-1].key>L.r[j].key) //逆序:前面的比后面的大,则交换
{
swap(L.r[j-1],L.r[j]);
flag=true;
}
}
if(flag==false) return;
}
}
2)快速排序
void QuickSort(ElemType A[],int low,int high)
{
if(low<high) //递归跳出条件
{
//partition()就是划分操作,将表A[low...high]划分为两个子表
int pivotopos=partition(A,low,high); //划分
QuickSort(A,low,privotopos-1); //依次对两个子表进行递归排序
QuickSort(A,privotopos+1,high);
}
}
int Partition(SqlList &L,int low,int high){
L.r[0]=L.r[low];
privotkey=L.r[low].key;//将当前表中的第一个元素设置为枢轴值,将表进行划分
while(low<high){ //循环跳出条件
while(low<high&&L.r[high].key>=privotkey) --high;
L.r[low]=L.r[high];
while(low<high&&L.r[low].key<=prvotkey) ++low;
L.r[high]=L.r[low];
}
L.r[low]=L.r[0]; //枢轴纪录到位
return low; //返回枢轴位置
}
3.选择排序
1)简单选择排序
void SeclectSort(SeqList &L){
for(int i=1;i<L.length;i++){ //让i从1到n-1进行n-1趟选择排序(最后一个位置不用再选择了)
min=i;
for(j=i+1;j<L.length;j++){
if(L.r[j].key<L.r[min].key)
min=j;
}
if(min!=i) swap(L.r[i]=L.r[min]);
}
}
2)堆排序
void HeapSort(HeapType &H)
{
//对顺序表进行堆排序
for(i=H.length/2;i>0;--i) //从最后一个非叶子结点开始建立堆
HeapAdjust(H,i,H.length);
for(i=H.length;i>1;--i){
//...输出堆顶元素
H.r[1]<-->H.r[i]; //将堆顶记录和最后一个记录交换
HeapAdjust(H,1,i-1); //重新建堆
}
}
void HeapAdjust(HeapAdjust &H,int s,int m){ //调整为大顶堆
rc=H.r[s]; //暂存堆顶元素
for(j=2*s;j<=m;j*=2){ //从上到下调整。让j指向堆顶左孩子,对其子树依次执行循环体(j*=2)
if(j<m&<(H.r[j].key,H.r[j+1].key)) ++j; //如果左孩子小于右孩子,那么把j指右孩子,如果j==m,说明j已经是最后一个结点了,没有右孩子
if(!LT(rc.key,H.r[j].key)) break; //比较堆顶与孩子结点大小,如果堆顶已经比孩子大,break;
H.r[s]=H.r[j]; //否则,把堆顶置换为孩子结点元素
s=j; //对其子树进行判断调整,以调整由于之前置换导致的子树下坠情况
}
H.r[s]=rc; //把暂存的元素插入最后空出的位置
}
4.归并排序
void Merge(RcdType SR[],RcdType TR[],int i,int m,int n){
//两个有序子序列的归并,SR中存待归并数据,TR是数据暂存的临时空间,i是SR第一个序列开始,m是第1个子序列结尾位置,n是第2个子序列结束位置
for(j=m+1,k=i;i<=m&&j<=n;++k){//j指向第二个子序列开始位置,k指向i指针对应位置(即让TR插入元素的起始位置与SR相同)
if(LQ(SR[i].key,SR[j].key)) TR[k]=SR[i++];
else TR[k]=SR[j++];
}
//判断哪个子序列还有剩余元素,全部复制到TR
if(i<=m) TR[k...n]=SR[i...m]; //伪代码,可以用while循环实现
if(j<=n) TR(k...n)=SR[j...n];
}
void MSort(RcdType SR[],RcdType &TR1[],int s,int t){
if(s==t) TR1[s]=SR[s];
else{
m=s+t/2;
MSort(SR,TR2,s,m);
MSort(SR,TR2,m+1,t);
Merge(TR2,TR1,s,m,t);
}
}
void MergeSort(SqlList &L){
Msort(L.r,L.r,1,L.length);
}
if(flag==false) return;
}
}
5.快速排序
void QuickSort(ElemType A[],int low,int high)
{
if(low<high) //递归跳出条件
{
//partition()就是划分操作,将表A[low...high]划分为两个子表
int pivotopos=partition(A,low,high); //划分
QuickSort(A,low,privotopos-1); //依次对两个子表进行递归排序
QuickSort(A,privotopos+1,high);
}
}
int Partition(SqlList &L,int low,int high){
L.r[0]=L.r[low];
privotkey=L.r[low].key;//将当前表中的第一个元素设置为枢轴值,将表进行划分
while(low<high){ //循环跳出条件
while(low<high&&L.r[high].key>=privotkey) --high;
L.r[low]=L.r[high];
while(low<high&&L.r[low].key<=prvotkey) ++low;
L.r[high]=L.r[low];
}
L.r[low]=L.r[0]; //枢轴纪录到位
return low; //返回枢轴位置
}