C语言数据结构

C语言数据结构

文章目录

前言

因为本人正在复习(预习)数据结构,所以本篇代码并不保证完全正确,仅用作学习用途

1.时间复杂度和空间复杂度

1.1算法效率的度量问题

接下来看一下这两段代码
在这里插入图片描述

  • 第一种算法执行了1+(n+1)+n=2n+2次。
  • 第二种算法,是1+1=2次
  • 如果我们把循环看做一个整体,忽略头尾判断的开销,那么这两个算法其实就是n和1的差距。

接下来再看一个例子
在这里插入图片描述

  • 这个例子中,循环条件i从1到100,每次都要让j循环100次,如果非常较真的研究总共精确执行次数那是非常累的。
  • 另一方面,我们研究算法的复杂度,侧重的是研究算法随着输入规模扩大增长量的一个抽象,而不是精确地定位需要执行多少次,因为如果这样的话我们就又得考虑回编译器优化等问题,然后,然后就永远也没有然后了!
  • 所以,对于刚才例子的算法,我们可以果断判定需要执行100^2次。
    在这里插入图片描述

1.2算法时间复杂度

算法时间复杂度的定义:

在进行算法分析时,语句总的执行次数T(n)是关于问题规模n的函数,进而分析T(n)随n的变化情况并确定T(n)的数量级。算法的时间复杂度,也就是算法的时间量度记作:T(n)=0(f(n))。
它表示随问题规模n的增大,算法执行时间的增长率和f(n)的增长率相同,称作算法的渐近时间复杂度,简称为时间复杂度。其中f(n)是问题规模n的某个函数。
执行次数=>时间
这样用大写0()来体现算法时间复杂度的记法,我们称之为大0记法。
一般情况下,随着输入规模n的增大,T(n)增长最慢的算法为最优算法。
显然,由此算法时间复杂度的定义可知,
我们的三个求和算法的时间复杂度分别为0(1),0(n),0(n^2)。 ( 1, n , n*n )

那么如何分析一个算法的时间复杂度呢?
即如何推导大0阶呢?我们给大家整理了以下攻略:

  • 用常数1取代运行时间中的所有加法常数。在修改后的运行次数函数中, 只保留最高阶项。
  • 如果最高阶项存在且不是1,则去除与这个项相乘的常数。

得到的最后结果就是大0阶。

1.2.1常数阶

在这里插入图片描述
0(8)? 这是初学者常常犯的错误,总认为有多少条语句就有多少。
分析下:

  • 按照我们的概念“T(n)是关于问题规模n的函数”来说,这里大家表示对鱼C的爱固然是好的,要支持的,要鼓励的,要大力表彰的。但是,跟问题规模有关系吗?没有,跟问题规模的表亲戚都没关系!,所以我们记作0(1)就可以。
  • 另外,如果按照攻略来,那就更简单了,攻略第一条就说明了所有加法常数给他个0(1)即可。

1.2.2线性阶

在这里插入图片描述

1.2.3平方阶

在这里插入图片描述
n等于100, 也就是说外层循环每执行一次,内层循环就执行100次,那总共程序想要从这两个循环出来,需要执行100*100次, 也就是n的平方。所以这段代码的时间复杂度为0(n^2)。

那如果有三个这样的嵌套循环呢?

没错,那就是n^3啦。所以我们很容易总结得出,循环的时间复杂度等于循环体的复杂度乘以该循环运行的次数。
注意:由于刚刚的执行循环次数是一样的,可以这么来算,但是如果不一样呢?
在这里插入图片描述
分析下,

  • 由于当i=0时,内循环执行了n次,当i=1 时,内循环则执行n-1次
  • 当i=n-1时,内循环执行1次,所以总的执行次数应该是:- n+(n-1)+(n-2)+…+1 = n(n+1)/2
    大家还记得这个公式吧?恩恩,没错啦,就是搞死先生发明的算法丫。
  • 那咱理解后可以继续,n(n+1)/2 = n^2/2+n/2用我们推导大0的攻略,第一条忽略,因为没有常数相加。第二条只保留最高项,所以n/2这项去掉。
  • 第三条, 去除与最高项相乘的常数,最终得0(n^2)。

1.2.4对数阶

在这里插入图片描述
由于每次i*2之后,就举例n更近一步,假设有x个2相乘后大于或等于n,则会退出循环。
于是由2^x = n得到x = log(2)n,所以这个循环的时间复杂度为0(logn)。

1.3常见的时间复杂度

例子时间复杂度术语
5201314O(1)常数阶
3n+4O(n)线性阶
3n2+4n+5O(n2)平方阶
3log(2)n+4O(logn)对数阶
2n+3nlog(2)n+14O(nlogn)nlogn阶
n3+2n2+4n+6O(n3)立方阶
2nO(2n)指数阶

在这里插入图片描述
在这里插入图片描述

1.3.1时间复杂度大小对比

常用的时间复杂度所耗费的时间从小到大依次是
0(1) < 0(logn) < (n) < 0(nlogn) <0(n2) < 0(n3) < 0(2n) < 0(n!) <0(nn)

  • 0(1),0(1ogn),0(n),0(n^2)我们前边已经给大家举例谈过了,至于0(nlogn)我们将会在今后的
    课程中介绍。
  • 而像0(n^3)之后的这些,由于n值的增大都会使得结果大得难以想象,我们没必要去讨论它们。

1.4算法的空间复杂度

算法的分析也是类似,我们查找一个有n个随机数字数组中的某个数字,

最好的情况是第一个数字就是,那么算法的时间复杂度为0(1),但也有可能这个数字就在最后一个位置,那么时间复杂度为0(n)。

平均运行时间是期望的运行时间。最坏运行时间是一种保证。
在应用中,这是一种最重要的需求,通常除非特别指定,我们提到的运行时间都是最坏情况的运行时间。

  • 通常,我们都是用时间复杂度”来指运行时间的需求,是用“空间复杂度”指空间需求。
  • 当直接要让我们求“复杂度”时,通常指的是时间复杂度。
  • 显然对时间复杂度的追求更是属于算法的潮流

2.线性表

2.1线性表的定义

线性表(List):
由零个或多个数据元素组成的有限序列。

这里需要强调几个关键的地方:

  • 首先它是一个序列, 也就是说元素之间是有个先来后到的。
  • 若元素存在多个,则第一个元素无前驱,而最后一个元素无后继,其他元素都有且只有一个前驱和后继。
  • 另外,线性表强调是有限的,事实上无论计算机发展到多强大,它所处理的元素都是有限的。

2.2定义考题模拟

请问公司的组织架构是否属于线性关系?
分析:
一般公司的总经理管理几个总监,每个总监管理几个经理,每个经理都有各自的下属和员工。
那这样的组织架构是不是线性关系呢?
当然不是啦!
注意线性关系的条件是如果存在多个元素,则“第一个元素无前驱,而最后一个元素无后继,其他元素都有且只有一个前驱和后继。

那么班级里同学之间的友谊呢?
当然也不是,因为每个人都会跟许多同学建立纯纯的友谊关系。

好,再来一题,那如果是情侣间的爱情关系呢?哈,还是扯淡,这要是线性关系还哪里容得下第三者? !
最后一个问题,一个班级里的点名册,是不是线性表?
是!!!

2.3常用函数的定义

  1. InitList(*L):初始化操作,建立一个空的线性表L。
  2. ListEmpty(L):判断线性表是否为空表,若线性表为空返回true,否则返回false。
  3. ClearList(*L):将线性表清空。 GetElem(L,i,*e):将线性表L中的第i个位置元素值返回给e
  4. LocateElem(L,e):在线性表L中查找与给定值e相等的元素,如果查找成功,返回该元素在表中序号表示成功;否则,返回0表示失败。
  5. ListInsert(*L,i,e):在线性表L中第i个位置插入新元素e。
  6. ListDelete(*L,i,*e):删除线性表L中第i个位置元素,并用e返回其值。
  7. ListLength(L):返回线性表L的元素个数。

2.3.1代码示例:数组实现线性表

#include<stdio.h>
#include<stdlib.h>
#define maxSize 100
typedef char DataType;
typedef struct node{
	DataType data[maxSize];
	int length;
}LinkNode;

//初始化 
void initList(LinkNode &L){
	L.length=0;
}

//创建 
void CreatList(LinkNode &L,DataType a[],int n){
	for(int i=0;i<n;i++){
		L.data[L.length]=a[i];
		L.length++;
	}
}

 //输出 
 void DispList(LinkNode &L){
 	for(int i=0;i<L.length;i++){
 		printf("%c ",L.data[i]);
	 }
	 printf("\n\n");
 }
 
 //查找第i个元素的值
 DataType GetElem(LinkNode &L,int i){
 	return L.data[i-1];
 } 
 
 //查找位置 
 int FindLocation(LinkNode &L,DataType e){
 	for(int i=0;i<L.length;i++){
 		if(L.data[i]==e){
 			return ++i;
		 }
	 }
	 return -1;
 } 
 
 //判断空 
 bool EmptyList(LinkNode &L){
 	return (L.length==0);
 }
 
 //长度 
 int Length(LinkNode &L){
 	return L.length;
 }
 
 //指定位置插入 
 void Insert(LinkNode &L,int n,DataType e){
 	L.length++;
 	if(n<=L.length){
 		for(int i=L.length-1;i>=n-1;i--){
 			L.data[i+1]=L.data[i];
		 }
		 L.data[n-1]=e;
	 }
 }
 //指定位置删除 
 void Remove(LinkNode &L,int n){
 	for(int i=n-1;i<L.length-1;i++){
 		L.data[i]=L.data[i+1];
	 }
	 L.length--; 
 }
 
 //清空
 void ClearList(LinkNode &L){
 	L.length==0;
 } 
 
 //正常添加 
 void addList(LinkNode &L,DataType e){
 	L.data[L.length]=e;
 	L.length++;
 }
 //主函数
int main(){
	LinkNode L;
	initList(L);
	DataType a[]={'a','b','c','d'};
	CreatList(L,a,4);
	printf("输出表:");
	DispList(L);
	printf("表的长度为%d\n\n",Length(L));
	printf("表是否为空?:%s\n\n",EmptyList(L)?"表示空的":"表不是空的");
	printf("第3个元素的值为%c\n\n",GetElem(L,3));
	printf("b的位置是第%d个\n\n",FindLocation(L,'b'));
	printf("在第三个位置插入t元素\n");
	Insert(L,3,'t');
	printf("插入完毕\n\n");
	printf("输出表:");
	DispList(L); 
	printf("删除第4个元素\n");
	Remove(L,4);
	printf("输出表:");
	DispList(L);
	printf("清空表");
	ClearList(L);
}

2.3.2代码示例:链表实现线性表

#include<stdio.h>
#include<stdlib.h>
#define maxSize 30
typedef int DataType;
typedef struct node{
	DataType data;
	struct node *next;
}CircNode;

//头插法 
void CreatListF(CircNode *&L,DataType a[],int n){
	CircNode *s;
	L=(CircNode *)malloc (sizeof (CircNode));
	L->next=NULL;
	for(int i=0;i<n;i++){
		s=(CircNode *)malloc (sizeof (CircNode));
		s->data=a[i];
		s->next=L->next;
		L->next=s; 
	}
}

//尾插法
 void CreatListR(CircNode *&L,DataType a[],int n){
 	CircNode *s;
 	CircNode *r;//尾指针 
	L=(CircNode *)malloc (sizeof (CircNode));
	r=L;
	for(int i=0;i<n;i++){
		s=(CircNode *)malloc (sizeof (CircNode));
		s->data=a[i];
		r->next=s;
		r=s;
	}
	r->next=NULL;
 }
 //输出 
 void DispList(CircNode *&L){
 	CircNode *p=L->next;
 	while(p!=NULL){
 		printf(" %d",p->data);
 		p=p->next;
	 }
	 printf("\n");
 }
 //查找第i个元素的值
 bool GetElem(CircNode *L,int i,DataType &e){
 	CircNode *p=L;
 	int j=0;
 	while(j<i&&p!=NULL){
 		j++;
 		p=p->next;
	 }
	 if(p==NULL){
	 	return false;
	 }else{
	 	e=p->data;
	 	return true;
	 }
 } 
 
 bool Insert(CircNode *&L,int i,DataType e){
 	CircNode *p=L,*s;
 	int j=0;
 	while(j<i-1&&p!=NULL){
 		j++;
 		p=p->next;
	 }
	 if(p==NULL){
	    return false; 
	 }else{
	 	s=(CircNode *)malloc (sizeof (CircNode));
		s->data=e;
		s->next=p->next;
		p->next=s;
	 }
 } 
 
 bool EmptyList(CircNode *&L){
 	return (L->next==NULL);
 }
 
 int lenth(CircNode *&L){
 	int n=0;
 	CircNode *p=L->next;
 	while(p!=NULL){
 		n++;
 		p=p->next;
	 }
	 return n;
 }

2.3.3代码示例:实现线性表的并运算

#include<stdio.h>
#include<stdlib.h>
#define maxSize 100
typedef char DataType;
typedef struct node{
	DataType data[maxSize];
	int length;
}LinkNode;

//初始化 
void initList(LinkNode &L){
	L.length=0;
}

//创建 
void CreatList(LinkNode &L,DataType a[],int n){
	for(int i=0;i<n;i++){
		L.data[L.length]=a[i];
		L.length++;
	}
}

 //输出 
 void DispList(LinkNode &L){
 	for(int i=0;i<L.length;i++){
 		printf("%c ",L.data[i]);
	 }
	 printf("\n\n");
 }
 
 //查找第i个元素的值
 DataType GetElem(LinkNode &L,int i){
 	return L.data[i-1];
 } 
 
 //查找位置 
 int FindLocation(LinkNode &L,DataType e){
 	for(int i=0;i<L.length;i++){
 		if(L.data[i]==e){
 			return ++i;
		 }
	 }
	 return -1;
 } 
 
 //判断空 
 bool EmptyList(LinkNode &L){
 	return (L.length==0);
 }
 
 //长度 
 int Length(LinkNode &L){
 	return L.length;
 }
 
 //指定位置插入 
 void Insert(LinkNode &L,int n,DataType e){
 	L.length++;
 	if(n<=L.length){
 		for(int i=L.length-1;i>=n-1;i--){
 			L.data[i+1]=L.data[i];
		 }
		 L.data[n-1]=e;
	 }
 }
 //指定位置删除 
 void Remove(LinkNode &L,int n){
 	for(int i=n-1;i<L.length-1;i++){
 		L.data[i]=L.data[i+1];
	 }
	 L.length--; 
 }
 
 //清空
 void ClearList(LinkNode &L){
 	L.length==0;
 } 
 
 //正常添加 
 void addList(LinkNode &L,DataType e){
 	L.data[L.length]=e;
 	L.length++;
 }
 //并运算主函数
int main(){
	LinkNode L1,L2,L3,L4;
	initList(L1);
	initList(L2);
	initList(L3);
	initList(L4);
	int num=0;
	DataType a1[]={'a','b','c','d','A'}; 
	DataType a2[]={'A','B','C','D','a','b'};
	CreatList(L1,a1,5);
	CreatList(L2,a2,6);
	printf("表L1的值为:");
	DispList(L1);
	printf("表L2的值为:");
	DispList(L2);
	for(int i=0;i<Length(L2);i++){
		addList(L3,GetElem(L2,i+1));
	}
	for(int j=0;j<Length(L1);j++){
		addList(L3,GetElem(L1,j+1));
	}
	printf("合并后的值为:");
	DispList(L3);
	for(int i=0;i<Length(L3);i++){
		for(int j=0;j<Length(L3);j++){
			if(GetElem(L3,j+1)==GetElem(L3,i+1)&&j!=i){
				num++;
				break;
			}
		}
		if(num==0){
			addList(L4,GetElem(L3,i+1));
		}
		num=0;
	}
	printf("实现了并运算之后的集合为:"); 
	DispList(L4);
} 

3.栈

3.1栈的定义

官方定义:栈(Stack)是一个后进先出(Lastin first out,LIFO) 的线性表,它要求只在表尾进行删除和插入操作。
小甲鱼定义:所谓的栈,其实也就是一个特殊的线性表(顺序表、链表),
但是它在操作上有一些特殊的要求和限制:

  • 栈的元素必须“后进先出”。
  • 栈的操作只能在这个线性表的表尾进行。
  • 注:对于栈来说,这个表尾称为栈的栈顶(top)相应的表头称为栈底(bottom)。

栈的插入操作(Push),叫做进栈,也称为压栈入栈。类似子弹放入弹夹的动作。
栈的删除操作(Pop),叫做出栈,也称为弹栈。如同弹夹中的子弹出夹。

3.2栈的顺序存储结构

因为栈的本质是一个线性表,线性表有两种存储形式,那么栈也有分为栈的顺序存储结构和栈的链式存储结构。

最开始栈中不含有任何数据,叫做空栈,此时栈顶就是栈底。然后数据从栈顶进入,栈顶栈底分离,整个栈的当前容量变大。数据出栈时从栈顶弹出,栈顶下移,整个栈的当前容量变小。
在这里插入图片描述

3.2.1代码示例:顺序栈

#include<stdio.h>
#include<stdlib.h>
#define initSize 50
typedef int SElemType;
typedef struct{
	SElemType *elem;
	int maxSize,top;
}SeqStack;
/**
	顺序栈4要素 
		栈空:top=-1
		栈满:top=maxSize-1 
		进栈e操作:top++;将e放在top处
		退栈操作:从top处取出元素,top-- 
**/
void InitStack(SeqStack &S){
	S.elem=(SElemType *)malloc (initSize*sizeof(SElemType));
	if(!S.elem){
		printf("内存分配错误\n");
		exit(1);
	}
	S.maxSize=initSize;
	S.top=-1;
}

int Push(SeqStack &S,SElemType x){
	if(S.top==S.maxSize-1)return 0;
	S.elem[++S.top]=x;
	return 1;
}

int Pop(SeqStack &S,SElemType &x){
	if(S.top==-1)return 0;
	x=S.elem[S.top--];
	return 1;
} 

int GetTop(SeqStack &S,SElemType &x){
	if(S.top==-1)return 0;
	x=S.elem[S.top];
	return 1;
}

int StackEmpty(SeqStack &S){
	return S.top==-1;
}

int StackFull(SeqStack &S){
	return S.top==S.maxSize;
}

int StackSize(SeqStack &S){
	return S.top+1;
}

int main(){
	SeqStack K;
	SElemType i;
	InitStack(K);
	printf("%s",StackEmpty(K)?"栈是空的\n":"栈不是空的\n");
	printf("插入5个值,4、5、6、7、8\n");
	Push(K,4);
	Push(K,5);
	Push(K,6);
	Push(K,7);
	Push(K,8);
	int size=StackSize(K);
	GetTop(K,i);
	printf("插入成功,现在栈有%d个值,栈的top值是%d\n\n",size,i);
	printf("",i);
	printf("从top退栈一次\n");
	Pop(K,i);
	printf("成功退出值:%d\n",i);
	size=StackSize(K);
	GetTop(K,i);
	printf("现在栈有%d个值,栈的top值是%d\n",size,i);
}

3.3栈的链式存储结构

讲完了栈的顺序存储结构,也给大家结合了一些例题演练,相信大家对栈再也不陌生了吧?

现在我们来看下栈的链式存储结构,简 称栈链。(通常我们用的都是栈的顺序存储结构存储,链式存储我们作为一个知识点,大家知道就好! )

栈因为只是栈顶来做插入和删除操作,所以比较好的方法就是将栈顶放在单链表的头部,栈顶指针和单链表的头指针合二为一。
在这里插入图片描述

3.3.1代码示例:链式栈

#include<stdio.h>
#include<stdlib.h>
typedef int SElemType;
typedef struct node{
	SElemType data;
	struct node *link;
}LinkNode,*LinkStack;

void InitStack(LinkStack &S){
	S=NULL;
}

int Push(LinkStack &S,SElemType x){
	LinkNode *p=(LinkNode *)malloc (sizeof(LinkNode));
	p->data=x;
	p->link=S;
	S=p;
	return 1;
}

int Pop(LinkStack &S,SElemType &x){
	if(S==NULL)return 0;
	LinkNode *p=S;
	x=p->data;
	S=p->link;
	free(p);
	return 1;
}

int GetTop(LinkStack &S,SElemType &x){
	if(S==NULL)return 0;
	x=S->data;return 1;
}

int StackEmpty(LinkStack &S){
	return S==NULL;
}

int StackSize(LinkStack &S){
	LinkNode *p=S;
	int k=0;while(p!=NULL){
		p=p->link;
		k++;
	}
	return k;
}

int main(){
	LinkStack K;
	SElemType i;
	InitStack(K);
	printf("%s",StackEmpty(K)?"栈是空的\n":"栈不是空的\n");
	printf("插入5个值,2,3,4,5,6\n");
	Push(K,2);
	Push(K,3);
	Push(K,4);
	Push(K,5);
	Push(K,6);
	int size=StackSize(K);
	GetTop(K,i);
	printf("插入成功,现在栈有%d个值,栈的top值是%d\n\n",size,i);
	printf("",i);
	printf("从top退栈一次\n");
	Pop(K,i);
	printf("成功退出值:%d\n",i);
	size=StackSize(K);
	GetTop(K,i);
	printf("现在栈有%d个值,栈的top值是%d\n",size,i);
}

4.队列

4.1队列的定义

队列(queue) 是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。与栈相反,队列是一种先进先出(First InFirst Out ,FIFO)的线性表。

与栈相同的是,队列也是一种重要的线性结构,实现一个队列同样需要顺序表或链表作为基础。
在这里插入图片描述

4.2代码示例:队列的顺序储存结构

#include<stdio.h>
#include<stdlib.h>
#define initSize 50
/**
*对空条件:rear=front 
*队满条件:Q.rear==Q.maxSize-1
*
*/

typedef int DataType;
typedef struct {
	DataType *data;
	int rear,front;
	int maxSize;
}SeqQueue;

//初始化 
void InitQueue(SeqQueue &Q){
	Q.data=(DataType *)malloc(initSize*sizeof(SeqQueue));
	Q.front=Q.rear=-1;
	Q.maxSize=initSize;
}

//入队
bool EnQueue(SeqQueue &Q,DataType e){
	if(Q.rear==Q.maxSize-1){
		return false;
	}
	Q.rear++;
	Q.data[Q.rear]=e;
	return true;
}

//出队 
bool DeQueue(SeqQueue &Q){
	if(Q.front==Q.rear){
		return false;
	}
	Q.front++;
	return true;
}

//判空 
bool QueueEmpty(SeqQueue &Q){
	return (Q.front==Q.rear);
}

int main(){
	SeqQueue sq;
	InitQueue(sq);
	printf("顺序对sq是空的吗?%s\n\n",QueueEmpty(sq)?"sq是空的":"sq不是空的");
	EnQueue(sq,6);
	EnQueue(sq,7);
	EnQueue(sq,8);
	EnQueue(sq,9);
	printf("开始删除\n\n");
	while(!QueueEmpty(sq)){
		printf("成功删除元素->%d\n",sq.data[sq.front+1]);
		DeQueue(sq);
	}
	printf("删除完毕,现在顺序对sq是空的吗?%s\n\n",QueueEmpty(sq)?"sq是空的":"sq不是空的");
}

4.3代码示例:队列的链式储存结构

因为我没有写链队的代码,所以用了csdn别的博主的代码
博客链接如右→博客链接

/**
*代码来自@凌盛羽 
*博客地址https://blog.csdn.net/Ceosat/article/details/103959740 
*/
#include<stdio.h>
#include<stdlib.h>
//队列节点 
typedef struct Node
{
	int dat;//结点值
	struct Node *pNext;//下一个结点
}Node, *pNode;
//Node   等效于 struct Node
//*pNode 等效于 struct Node *


//链式队列
typedef struct LinkQueue
{
	struct Node * qFront;//队首指针
	struct Node * qRear;//队尾指针
}LinkQueue, *pLinkQueue;
//LinkQueue  等效于 struct LinkQueue
//pLinkQueue 等效于 struct LinkQueue *

//创建链式队列
LinkQueue * CreatLinkQueue(void)
{
	pLinkQueue pHeadQueue = NULL;//链式队列指针
	pNode pHeadNode = NULL;//头结点指针


	//为链式队列申请内存
	pHeadQueue = (LinkQueue *)malloc(sizeof(LinkQueue));
	if (pHeadQueue == NULL)
	{
		printf("链式队列内存申请失败,程序终止......\r\n");
		while (1);
	}

	//为链式队列头结点申请内存
	pHeadNode = (Node *)malloc(sizeof(Node));
	if (pHeadNode == NULL)
	{
		printf("链式队列头结点内存申请失败,程序终止......\r\n");
		while (1);
	}
	
	pHeadQueue->qFront = pHeadNode;//队首指向头结点
	pHeadQueue->qRear  = pHeadNode;//队尾指向头结点
	pHeadNode->pNext   = NULL;//头结点无下个结点
	pHeadNode->dat     = 0;//头结点数据为零

	printf("链式队列创建成功......\r\n");
	printf("头结点:0x%08X    头结点指针:0x%08X    队首指针:0x%08X    队尾指针:0x%08X\r\n", pHeadNode, pHeadNode->pNext, pHeadQueue->qFront, pHeadQueue->qRear);

	return pHeadQueue;
}

//链式队列数据入队
void EnterLinkQueue(pLinkQueue queue, int value)
{
	pNode newNode = NULL;//链式队列入队结点指针


	//为链式队列入队结点申请内存
	newNode = (Node *)malloc(sizeof(Node));
	if (newNode == NULL)
	{
		printf("链式队列入队结点内存申请失败......\r\n");
		return;
	}

	queue->qRear->pNext = newNode;//入队新结点为最后个结点
	queue->qRear   = newNode;//队尾指向入队新结点
	newNode->pNext = NULL;//入队新结点无下个结点
	newNode->dat   = value;//入队值

	printf("入队成功!入队值:%d  ---->  ", value);
	printf("队首结点指针:0x%08X    队尾指针:0x%08X\r\n", queue->qFront, queue->qRear);
}
//判断链式队列是否为空
bool IsEmptyLinkQueue(pLinkQueue queue)
{
	//队首与队尾指向同一节(首节点)点则队列为空
	if (queue->qFront == queue->qRear)
		return true;
	else
		return false;
}
//遍历链式队列数据
void TraverseLinkQueue(pLinkQueue queue)
{
	pNode queNode = NULL;//结点指针

	if (IsEmptyLinkQueue(queue))
	{
		printf("链式队列为空,遍历失败......\r\n");
		return;
	}

	printf("链式队列数据: ");
	queNode = queue->qFront->pNext;//第一个有效结点
	while (queNode != NULL)//最后一个结点结束
	{
		printf("%d ", queNode->dat);//结点数据
		queNode = queNode->pNext;//下一个结点
	}
	printf("\r\n");
}
//链式队列数据出队
void OutLinkQueue(pLinkQueue queue, int * value)
{
	pNode queNode = 0;//队列结点指针

	if (IsEmptyLinkQueue(queue))
	{
		printf("链式队列为空,出队失败......\r\n");
		*value = 0;
		return;
	}

	if (queue->qFront->pNext == queue->qRear)//只有一个有效结点
		queue->qRear = queue->qFront;//队尾指针等于队首指针

	queNode = queue->qFront->pNext;//结点指针指向队首有效结点
	queue->qFront->pNext = queNode->pNext;//队首结点指针指向下个结点
	*value = queNode->dat;//出队结点值
	free(queNode);//释放内存

	printf("出队成功!出队值:%d  ---->  ", *value);
	printf("队首结点指针:0x%08X    队尾指针:0x%08X\r\n", queue->qFront->pNext, queue->qRear);
}
//获取链式队列长度
int CountLinkQueue(pLinkQueue queue)
{
	pNode queNode = NULL;//结点指针
	int len = 0;

	if (IsEmptyLinkQueue(queue))
	{
		printf("链式队列为空......\r\n");
		return len;
	}

	queNode = queue->qFront->pNext;//第一个有效结点
	while (queNode != NULL)//最后一个结点结束
	{
		len++;
		queNode = queNode->pNext;//下一个结点
	}
	
	printf("链式队列长度: %d\r\n", len);
	return len;
}

int main()
{
	pLinkQueue Queue;
	int delVal = 0;
	Queue =  CreatLinkQueue();//创建链式队列
	printf("\r\n");
	EnterLinkQueue(Queue, 10);//链式队列数据入队
	EnterLinkQueue(Queue, 20);
	EnterLinkQueue(Queue, 30);
	EnterLinkQueue(Queue, 40);
	EnterLinkQueue(Queue, 50);
	EnterLinkQueue(Queue, 60);
	CountLinkQueue(Queue);//获取链式队列长度
	TraverseLinkQueue(Queue);//遍历链式队列数据
	printf("\r\n");
	OutLinkQueue(Queue, &delVal);//链式队列数据出队
	OutLinkQueue(Queue, &delVal);
	OutLinkQueue(Queue, &delVal);
	OutLinkQueue(Queue, &delVal);
	OutLinkQueue(Queue, &delVal);
	CountLinkQueue(Queue);//获取链式队列长度
	TraverseLinkQueue(Queue);//遍历链式队列数据
	printf("\r\n");
}

5.字符串

5.1字符串的定义

我们来研究下"串"这样的数据结构:

定义:
串(String)是由零个或多个字符组成的有限序列,又名叫字符串

  • 一般记为S=“a1a2a3.。…an”(n>=0)
  • 串可以是空串,即没有字符,直接由表示(注意里边没有空格哦~),或者可以用希腊字母中φ来表示(读fai,四声)
  • 子串与主串,例如"FishC"是“FishC. com’的子串,反之则倒过来。

5.2字符串的比较

字符串比较大小跟传统的数字比较有点差别,很容易我们可以知道2比1要大,可要是“FishC”和“fishc.com”呢?要怎么比较?比长短?比大小?

比大小! 没错,比的就是字符串里每个字符的ASCII码大小,因为“F”=70,“f”=102,“f”>“F”
所以“fishc.com”>“FishC”

其实这样的比较大小没有多大意义,字符串的比较我们更重视是否相等!

5.3代码示例:字符串的存储结构

定义一个SqlString.h(接下来会进行调用这个头文件)

#include<stdio.h>
#define maxSize 100
typedef struct{
	char str[maxSize];
	int length;
}SqlString;

void CreatString(SqlString &s,char a[]){
	s.length=0;
	int i;
	for(i=0;i<maxSize&&a[i]!='\0';i++){
		s.str[s.length]=a[i];
		s.length++;
	}
}

void Disp(SqlString s){
	int i=0;
	printf("字符串是->'");
	while(i<s.length){
		printf("%c",s.str[i]);
		i++;
	}
	printf("'\t长度为:%d\n\n",s.length);
}

void StringCopy(SqlString &s1,SqlString &s2){
	s2.length=s1.length;
	for(int i=0;i<s1.length;i++){
		s2.str[i]=s1.str[i];
	}
}

bool StrEquals(SqlString s1,SqlString s2){
	if(s1.length!=s2.length){
		return false;
	}else{
		for(int i=0;i<s1.length;i++){
			if(s1.str[i]!=s2.str[i]){
				return false;
			}
		}
	}
	return true;
}

void ConnectString(SqlString &s1,SqlString &s2){
	int mark=s1.length;
	s1.length+=s2.length;
	int j=0;
	for(int i=mark;j<s2.length;i++){
		s1.str[i]=s2.str[j];
		j++;
	} 
}


5.4代码示例:比较俩字符串是否一致

#include"SqlString.h"
int main(){
	SqlString s1,s2;
	char a[6]={'C','h','i','n','a','\0'};
	CreatString(s1,a);
	Disp(s1);
	printf("此时定义一个SqlString类型的s2,执行复制方法\n\n");
	StringCopy(s1,s2);
	printf("s1和s2相等吗?:%s\n",StrEquals(s1,s2)?"s1与s2完全等值\n":"s1与s2不是完全相等的\n");
	printf("s1和s2连接\n");
	ConnectString(s1,s2);
	Disp(s1);
}

5.5代码示例:俩字符串是否匹配(是否为子串主串关系)

#include"SqlString.h"
int BFString(SqlString T,SqlString P);
int KMPString(SqlString T,SqlString P,int next[]);
int main(){
	//第一种情况 
	char s1[20]="abcdefghij";
	char s2[10]="cdef";
	SqlString T1,P1;
	CreatString(T1,s1);
	Disp(T1);
	CreatString(P1,s2);
	Disp(P1);
	int result=BFString(T1,P1);
	printf("%s",result==-1?"俩字符串不匹配\n":"字符串匹配\n");
	if(result!=-1){
		printf("字符串位置为:%d\n\n",result);
	}
	//第二组情况 
	char s3[20]="abcuerghij";
	char s4[10]="cdef";
	SqlString T2,P2;
	CreatString(T2,s3);
	Disp(T2);
	CreatString(P2,s4);
	Disp(P2);
	result=BFString(T2,P2);
	printf("%s",result==-1?"俩字符串不匹配\n":"字符串匹配\n");
	if(result!=-1){
		printf("字符串位置为:%d",result);
	}
}
int BFString(SqlString T,SqlString P){
	int i=0,j=0;
	while(i<T.length&&j<P.length){
		if(T.str[i]==P.str[j]){
			i++;
			j++;
		}else{
			i=i-j+1;
			j=0;
		}
	}
	if(j==P.length){
		return (i-j);
	}else{
		return -1;
	}
}

6.树与二叉树

6.1树的定义

树(Tree)是n(n>=0)个结点的有限集。

当n=0时成为空树,在任意一棵非空树中:

  • 有且仅有一个特定的称为根(Root)的结点;
  • 当n>1时,其余结点可分为m(m>0)个互不相交的有限集T1、T2、Tm,其中每一个集合本身又是一棵树,并且称为根的子树(SubTree)。
    在这里插入图片描述

注意:

  • n>0时,根结点是唯一的,坚决不可能存在多个根结点。
  • m>0时,子树的个数是没有限制的,但它们互相是一定不会相交的。

6.2基本术语

在这里插入图片描述

  1. 结点:树中的一个独立单位。包含一个数据元素及若干指向其子树的分支,如图中的每个字母都是一个结点。

  2. 结点的度:结点所拥有的子树数称为结点的度,如D的度为3,E的度数为1。

  3. 树的度:树的度是树内各结点度的最大值,上图的树的度为3.

  4. 叶子:度为0的结点称为叶子或终端结点。如上图的G,H,I,J,F.

  5. 非终端结点:度不为0的结点称为非终端结点或分支结点。除根结点之外,也称为内部结点。如上图的A,B,C,D,E.

  6. 双亲和孩子:若一个结点含有子结点,则这个结点称为其子节点的双亲节点。如上图的D的双亲结点为B

  7. 兄弟:具有同个双亲的结点互为兄弟结点。如上图的G,I,H

  8. 祖先:从根到该结点所经分支上的所有结点。如上图的J的祖先为A,C,E

  9. 子孙:以某个结点为根的子树中的任一结点都是该根结点的子孙。如上图B的子孙有D,G,H,I

  10. 层次:结点的层次从根开始定义,如上图的层次说明。

  11. 堂兄弟:双亲在同一层的结点互为堂兄弟。例如结点D的堂兄弟为E,F.

  12. 树的深度:树中结点的最大层次称之为树的深度或高度。如上图的深度为4.

  13. 有序树和无序树:如果将树中结点的各子树看成从左至右是由次序的,则称该树的为有序树,否则为无序树。

6.3树的储存结构

6.3.1双亲储存结构

一种顺序存储结构,用一组连续空间存储树的所有结点,同时在每个结点中附设一个伪指针指示其双亲结点的位置

typedef struct{
	ElemType data;			//存放结点的值
	int parent;				//存放双亲的位置
} PTree[MaxSize]			//PTree为双亲存储结构类型

6.3.2孩子链存储结构

每个结点不仅包含结点值,还包括指向所有孩子结点的指针

typedef struct{
	ElemType data;					//结点的值
	struct node *sons[MaxSons];		//指向孩子结点
} TSonNode;							//孩子链存储结构中的结点类型

6.3.3孩子兄弟链存储结构

为每个结点设计3个域,即一个数据元素域,一个指向该结点的左边第一个孩子结点(长子)的指针域,一个指向该结点的下一个兄弟结点的指针域

typedef struct tnode{
	ElemType data;			//结点的值
	struct tnode *hp;		//指向兄弟
	struct tnode *vp;		//指向孩子结点
}	TSBNode;				//孩子兄弟链存储结构中的结点类型

6.4二叉树

6.4.1二叉树的定义

二叉树是一个有限的结点集合,这个集合或者为空,或者由一个根结点和两课互不相交的称为左子树和右子树的二叉树组成

6.4.1.1二叉树和树的区别
  1. 度为2的树中至少有一个结点的度为2,而二叉树没有这种要求
  2. 度为2的树不区分左,右子树,而二叉树是严格区分左,右子树的
6.4.1.2满二叉树

一棵二叉树中所有分支结点都有左孩子结点和右孩子结点,并且叶子结点都集合在二叉树的最下一层

6.4.1.3非空满二叉树
  1. 叶子结点都在最下一层
  2. 只有度为0和度为2的结点

6.4.1.4非空完全二叉树

  1. 叶子结点只可能在最下面两层中出现
  2. 对于最大层次中的叶子结点,都依次排列在该层最左边的位置上
  3. 如果有度为1的结点,只可能出现一个,且该结点只有左孩子而无右孩子
  4. 按层次编号时,一旦出现编号为i的结点是叶子结点或只有左孩子,则编号大于i的结点均为叶子结点当结点总数n为奇数时,ni=0,当结点总数n为偶数时,n1=1

6.4.2二叉树的性质

  1. 非空二叉树上的叶子结点数等于双分支结点数加1
  2. 非空二叉树的第i层上最多有2i-1个结点(i≥1)
  3. 高度为n的二叉树最多有2ⁿ-1个结点(n≥1)
  4. 具有n个(n>0)结点的完全二叉树的高度为[log₂(n+1)]或[log₂n]+1

6.4.3二叉树的存储结构

6.4.3.1二叉树的顺序存储结构
#define MAXSIZE 100
typedef TElemType SqBiTree[MAXSIZE];
SqBiTree bt;

假设为 typedef int sqBiTree[10] ,SqBiTree bt ,相当于定义一个int bt[10]

6.4.3.2二叉树的链式存储结构

在这里插入图片描述
^代表的是空NULL

typedef struct node{
	ElemType data;			//数据元素
	struct node *lchild;	//指向左孩子结点
	struct node *rchild;	//指向右孩子结点
}	BTNode;

6.4.4二叉树的遍历

6.4.4.1先序遍历

void preOrder(BitTree t)      //先序遍历
{
    if(t)             //树不为空
    {
        visit(t->data );        //访问节点数据,自定义访问函数visit
        preOrder (t->lchild );      //递归遍历左子树
        preOrder (t->rchild );      //递归遍历右子树
    }

6.4.4.2中序遍历
void inOrder(BitTree t)       //中序遍历
{ 
    if(t)          //树不为空
    {
        inOrder (t->lchild );
        visit(t->data );        //访问节点数据
        inOrder (t->rchild );
    }
}
6.4.4.3后序遍历
void postOrder(BitTree t)     //后序遍历
{
    if(t)           //树不为空
    {
        postOrder (t->lchild );
        postOrder (t->rchild );
        visit(t->data );        //访问节点数据
    }
}

6.4.5代码示例:二叉树的运算实现

//创建二叉树 
    int createBTNode(BTNode * &BT,char *str,int n);
    //销毁二叉树
    void destroyBTNode(BTNode * &BT);
    //查找结点存在
    BTNode *findBTNode(BTNode * &BT,char ch);
    //求高度
    int BTHeight(BTNode * &BT);
    //输出二叉树
    void displayBTNode(BTNode * &BT);
    //先序遍历
    void preOrder(BTNode * &BT);
    //中序遍历
    void inOrder(BTNode * &BT);
    //后序遍历
    void postOrder(BTNode * &BT);

完整代码如下

#include<stdio.h>
#include<malloc.h>

typedef struct node{
	struct node *lchild;						//指向左孩子节点
	char data;									//数据元素
	struct node *rchild;						//指向右孩子节点 
}BTNode;

//创建二叉树 
int createBTNode(BTNode * &BT,char *str,int n);
//销毁二叉树
void destroyBTNode(BTNode * &BT);
//查找结点存在
BTNode *findBTNode(BTNode * &BT,char ch);
//求高度
int BTHeight(BTNode * &BT);
//输出二叉树
void displayBTNode(BTNode * &BT);
//先序遍历
void preOrder(BTNode * &BT);
//中序遍历
void inOrder(BTNode * &BT);
//后序遍历
void postOrder(BTNode * &BT);

//创建 
int createBTNode(BTNode * &BT,char *str,int n){	
	printf("%d ",n);
	char ch=str[n];								//把第 n 个字符赋给ch,方便后面判断 
	printf("%c \n",ch);
	n=n+1;
	if(ch!='\0'){								//如果 ch 不等于结束符就继续创建,否则就结束 
		if( ch=='#'){							//以 # 号代表 NULL,下面没有了 
			BT = NULL;
		}
		else{
			BT = new BTNode;					//新建一个二叉链 
			BT->data=ch;						//把字符存入二叉链 
			n=createBTNode(BT->lchild,str,n); 	//左递归创建 
			n=createBTNode(BT->rchild,str,n);	//右递归创建 
		}
	}
	return n;									//返回 n,记录字符串使用到哪里了 
}

//摧毁 
void destroyBTNode(BTNode * &BT){
	if(BT!=NULL){
		destroyBTNode(BT->lchild);				//左递归释放内存 
		destroyBTNode(BT->rchild);				//右递归释放内存 
		
		/*
			free()释放的是指针指向的内存!注意!释放的是内存,不是指针!这点非常非常重要!
			指针是一个变量,只有程序结束时才被销毁。释放内存空间。 
			原来指向这块空间的指针还是存在!只不过现在指针指向的内容是未定义的。
			因此,释放内存后把指针指向NULL,防止指针在后面不小心又被引用了。非常重要啊这一点!
		*/
		free(BT);
		BT=NULL; 
	}
}

//查找节点
BTNode *findBTNode(BTNode * &BT,char ch){
	if(BT==NULL){								//空,返回为空 NULL 
		return NULL;
	}
	else if(BT->data==ch){						//存在,提示存在并返回数据 
		printf("存在该节点:%c",ch); 
		return BT;
	}
	else{
		BTNode *p;								//定义一个链表指针 
		p=findBTNode(BT->lchild,ch);			//递归查询左子树 
		if(p!=NULL){
			return p;							//左子树已经找到 
		}
		else{
			return findBTNode(BT->rchild,ch);	//递归查询右子树 
		}
	}
}

//求高度
int BTHeight(BTNode * &BT){
	int lchildh;
	int rchildh;
	int h;
	if(BT==NULL){
		return 0;										//空树高度为0 
	}
	else{
		lchildh=BTHeight(BT->lchild);					//求左子树的高度 
		rchildh=BTHeight(BT->rchild);					//求右子树的高度 
		h=(lchildh>rchildh)?(lchildh+1):(rchildh+1);	//比较左子树和右子树,高度高的再 +1(根节点) 就是树的高度 
		return h;
	}
}

//输出
void displayBTNode(BTNode * &BT){
	if(BT!=NULL){
		printf("%c",BT->data);
		if(BT->lchild!=NULL || BT->rchild!=NULL){
			printf("(");
			displayBTNode(BT->lchild);
			printf(",");
			displayBTNode(BT->rchild);
			printf(")");
		}
	}
}
void displayBTNode1(BTNode * &BT){
	if(BT!=NULL){
		printf("%c",BT->data);
		displayBTNode1(BT->lchild);
		displayBTNode1(BT->rchild);
	}
	else{
		printf("#");
	}
}

//先序遍历
void preOrder(BTNode * &BT){
    if(BT!=NULL){					//判断不为空 
        printf("%c",BT->data);		//访问根节点
        preOrder(BT->lchild);		//递归,先序遍历左子树 
        preOrder(BT->rchild);		//递归,先序遍历右子树 
    }
}

//中序遍历
void inOrder(BTNode * &BT){
    if(BT!=NULL){
        inOrder(BT->lchild);
        printf("%c",BT->data);
        inOrder(BT->rchild);
    }
}
	
//后序遍历
void postOrder(BTNode * &BT){
    if(BT!=NULL){
        postOrder(BT->lchild);
        postOrder(BT->rchild);
        printf("%c",BT->data);
    }
}

int main(){
	
	//例子:ABC###D##
	BTNode *BT;
	printf("输入字符串:");
	char *str=(char *)malloc(sizeof(char) * 1024);
	scanf("%s",str); 
	createBTNode(BT,str,0);
	printf("二叉树建立成功\n");
	
//	destroyBTNode(BT);
//	if(BT==NULL){
//		printf("销毁成功\n");
//	}
	
	printf("请输入要查找的节点:");
	char c='E';
	printf("%c\n",c); 
	if(findBTNode(BT,c)==NULL){
		printf("没有此节点");
	}
	printf("\n");
	
	int h=BTHeight(BT); 
	printf("树的高度为:%d",h);
	printf("\n");
	
	printf("二叉树为:"); 
	displayBTNode(BT);
	printf("\n");
	printf("二叉树为:"); 
	displayBTNode1(BT);
	printf("\n");
	
	printf("先序遍历结果:");
	preOrder(BT);
	printf("\n");
	
	printf("中序遍历结果:");
	inOrder(BT);
	printf("\n");
	
	printf("后序遍历结果:");
	postOrder(BT);
	printf("\n");
	
	return 0;
}  

结果

输入字符串:ABC###D##
0 A
1 B
2 C
3 #
4 #
5 #
6 D
7 #
8 #
二叉树建立成功
请输入要查找的节点:E
没有此节点
树的高度为:3
二叉树为:A(B(C,),D)
二叉树为:ABC###D##
先序遍历结果:ABCD
中序遍历结果:CBAD
后序遍历结果:CBDA

7.图

7.1.图的定义

图(Graph) 是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为: G(V,E), 其中,G表示一个图,V是图G中顶点的集合,E是图G中边的集合。

对于图的定义,我们需要明确几个注意的地方:

  • 线性表中我们把数据元素叫元素,树中叫结点,在图中数据元素我们则称之为顶点(Vertex)。
  • 线性表可以没有数据元素,称为空表,树中可以没有结点,叫做空树,而图结构在咱国内大部分的教材中强调顶点集合V要有穷非实。
  • 线性表中,相邻的数据元素之间具有线性关系,树结构中,相邻两层的结点具有层次关系,而图结构中,任意两个顶点之间都可能有关系,顶点之间的逻辑关系用边来表示,边集可以是空的。

无向边
在这里插入图片描述
有向边
在这里插入图片描述
简单图
在这里插入图片描述
无向完全图
在这里插入图片描述
有向完全图
在这里插入图片描述
稀疏稠密图
在这里插入图片描述
子图
在这里插入图片描述
顶点与边的关系
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
连通图
在这里插入图片描述
在这里插入图片描述

7.2.图的储存结构

图的存储结构相比较线性表与树来说就复杂很多。

我们回顾下,对于线性表来说,是一对一的关系,所以用数组或者链表均可简单存放。树结构是一对多的关系,所以我们要将数组和链表的特性结合在一起才能更好的存放。

那么我们的图,是多对多的情况,另外图上的任何一个顶点都可以被看作是第一个顶点,任一顶点的邻接点之间也不存在次序关系。

7.2.1邻阶矩阵(无向图)

考虑到图是由顶点和边或弧两部分组成,合在一起比较困难,那就很自然地考虑到分为两个结构来分别存储。

顶点因为不区分大小、主次,所以用一个一维数组来存储是狠不错的选择。

而边或弧由于是顶点与顶点之间的关系,一维数组肯定就搞不定了,那我们不妨考虑用一个二维数组来存储。

于是我们的邻接矩阵方案就诞生了!
在这里插入图片描述
arc[0][1]=1代表V0到V1之间有边

同理arc[1][3]=0代表V1到V3之间无边

arc[x][x]永远为0(本身和本身之间无边)

我们可以设置两个数组,顶点数组为vertex[4]={V0,V1,V2,V3},边数组arc[4][4]为对称矩阵(0表示不存在顶点间的边,1表示顶点间存在边)。

对称矩阵:所谓对称矩阵就是n阶矩阵的元满足a[i][j]=a[j]i。 即从矩阵的左上角到右下角的主对角线为轴,右上角的元与左下角相对应的元全都是相等的。

有了这个二维数组组成的对称矩阵,我们就可以很容易地知道图中的信息:

  1. 要判定任意两顶点景否有边无边就非常容易了;
  2. 要知道某个顶点的度,其实就是这个顶点Vi在邻接矩阵中第i行(或第i列)的元素之和;
  3. 求顶点Vi的所有邻接点就是将矩阵中第i行元素扫描一遍,arc[i][j]为1就是邻接点。

7.2.2邻阶矩阵(有向图)

无向图的边构成了一个对称矩阵,貌似浪费了一半的空间,那如果是有向图来存放,会不会把资源都利用得很好呢?

在这里插入图片描述
tips:先看行,再看列
arc[1][0]=1代表V1指向了V0

arc[1][4]=0代表V1没有指向V3

arc[x][x]永远为0

可见顶点数组vertex[4]={V0,V1,V2,V3},弧数组arc[4][4]也是一个矩阵

但因为是有向图,所以这个矩阵并不对称,例如由V1到V0有弧,得到arc[1][0]=1,而V0到V1没有 弧,因此arc[0][1]=0。

另外有向图是有讲究的,要考虑入度和出度,顶点V1的入度为1,正好是第V1的各数之和,顶点V1的出度为2,正好是第V2的各数之和

tips:出、入 对应 行、列

7.2.3邻接矩阵(网)

在图的术语中,我们提到了网这个概念,事实上也就是每条边上带有权的图就叫网

在这里插入图片描述
这里无穷符号表示一个计算机允许的、大于所有边上权值的值。

7.2.4邻接表(无向图)

邻接矩阵看上去是个不错的选择,首先是容易理解,第二是索引和编排都很舒服

但是我们也发现,对于边数相对顶点较少的图,矩阵结构无疑是存在对存储空间的极大浪费。

因此我们可以考虑另外一种存储结构方式,例如把数组与链表结合一起来存储,这种方式在图结构也适用,我们称为邻接表(AdjacencyList)。

邻接表的处理方法是这样:

  • 图中顶点用一个一维数组存储,当然,顶点也可以用单链表来存储,不过数组可以较容易地读取顶点信息,更加方便。
  • 图中每个顶点Vi的所有邻接点构成一个线性表,由于邻接点的个数不确定,所以我们选择用单链表来存储。

在这里插入图片描述
first指针依次指向下一个结点,最后一个结点的指针应该加上^代表没有下一个结点(该结点为最后一个结点)

7.2.5邻接表(有向图)

若是有向图,邻接表结构也是类似的,我们先来看下把顶点当弧尾建立的邻接表,这样很容易就可以得到每个顶点的出度:
在这里插入图片描述
但也有时为了便于确定顶点的入度或以顶点为弧头的弧,我们可以建立一个有向图的逆邻接表:
在这里插入图片描述
此时我们很容易就可以算出某个顶点的入度或出度是多少,判断两顶点是否存在弧也很容易实现。

7.2.6邻接表(网)

对于带权值的网图,可以在边表结点定义中再增加一个数据域来存储权值即可:
在这里插入图片描述

7.2.7邻接表和邻接矩阵的代码

#include <stdio.h>
#include <stdlib.h>
#define INF 32767  //定义∞ 
#define MAXV  100  //最大顶点个数
typedef char DataType;
typedef struct
{
	int no;
	DataType info;
 } VertexType;       //顶点类型
 typedef struct
 {
 	int edges[MAXV][MAXV];//领接矩阵数组 
 	int n,e;                 //顶点数、边数 
 	VertexType vex[MAXV];  //存放顶点信息 
  } MatGraph;             //图领接矩阵类型
  typedef struct ANode
  {
  	int adjvex;          //该边的邻接点编号 
  	struct ANode *nextarc; //指向下一条边的指针 
  	int weight;       //该边的权重 
   } ArcNode;        //边结点类型 
   typedef struct Vnode
   {
   	   DataType info; //顶点其他信息 
		  int count;    
		  ArcNode *firstarc;  //指向第一条边 
   }VNode;         //邻接表头结点类型 
   typedef struct
   {
   	Vnode adjlist[MAXV];
   	int n,e; 
   }AdjGraph;
   //创建图的领接矩阵
   void CreateMat(MatGraph &g,int A[MAXV][MAXV],int n,int e)
   {
   	  int i,j;
   	  g.n=n;
		 g.e=e;
		 for(i=0;i<g.n;i++)
		 for(j=0;j<g.n;j++)
		 g.edges[i][j]=A[i][j];	
   } 
   //输出领接矩阵g
 void   DispMat(MatGraph g)
 {
 	int i,j;
 	for(i=0;i<g.n;i++)
 	{
 		for(j=0;j<g.n;j++)
 		if(g.edges[i][j]!=INF)
 		printf("%4d",g.edges[i][j]);
 		else
 		printf("%4s","∞");
 		printf("\n");
	 }
  } 
   //邻接表的基本运算 
   void CreateAdj(AdjGraph *&G,int A[MAXV][MAXV],int n,int e)
   {
   	  int i,j;
   	  ArcNode *p;
   	  G=(AdjGraph *)malloc(sizeof(AdjGraph ));
   	  for(i=0;i<n;i++)
   	  G->adjlist[i].firstarc=NULL;
   	  for(i=0;i<n;i++)
   	  for(j=n-1;j>=0;j--)
   	  	if(A[i][j]!=0&&A[i][j]!=INF)
   	  	{
   	  		p=(ArcNode*)malloc (sizeof(ArcNode));
   	  		p->adjvex=j;
   	  		p->weight=A[i][j];
   	  		p->nextarc=G->adjlist[i].firstarc;
   	  		G->adjlist[i].firstarc=p;
			 }  	  	
   	    G->n=n;
   	    G->e=n;		 	
   } 
   
   void DispAdj(AdjGraph *G)
   {
   	ArcNode *p;
   	for(int i=0;i<G->n;i++)
   	{
   		p=G->adjlist[i].firstarc;
   		printf("%3d:",i);
   		while(p!=NULL)
   		{
   			printf("%3d[%d]->",p->adjvex,p->weight);
   			p=p->nextarc;
		}
		   printf("^\n");
	}
} 

int main() {  
  MatGraph g;  
  AdjGraph *G;  
  int A[MAXV][MAXV]={{0,5,INF,7,INF,INF},{INF,0,4,INF,INF,INF},{8,INF,0,INF,INF,9},{INF,INF,5,0,INF, 6},{INF,INF,INF,5,0,INF},{3,INF,INF,INF,1,0}};  
  int n=6,e=10;  
  CreateMat(g,A,n,e);  
  printf("图 G 的领接矩阵:\n");  
  DispMat(g);  
  CreateAdj(G,A,n,e);  
  printf("图 G 的邻接表:\n");  
  DispAdj(G);   
} 

7.3图的遍历

树的遍历我们谈了四种方式,大家回忆一下,树因为根结点只有一个,并且所有的结点都只有一个双亲,所以不是很难理解。

但是谈到图的遍历,那就复杂多了,因为它的任一顶点都可以和其余的所有顶点相邻接,因此极有可能存在重复走过某个顶点或漏了某个顶点的遍历过程。

对于图的遍历,如果要避免以上情况,那就需要科学地设计遍历方案,通常有两种遍历次序方案:他们是深度优先遍历广度优先遍历

7.3.1深度优先遍历

深度优先遍历(DepthFirstSearch),也有称为深度优先搜索,简称为DFS。

它的具体思想类似于课程开头讲的找钥匙方案,无论从哪一间房间开始都可以,将房间内的墙角床头柜、床上、床下、衣柜、电视柜等挨个寻找,做到不放过任何一个死角,当所有的抽屉、储藏柜中全部都找遍,接着再寻找下一个房间。

我们可以约定右手原则:在没有碰到重复顶点的情况下,分叉路口始终是向右手边走,每路过一个顶点就做一个记号。

所以深度优先遍历实际上就是一个递归的过程

7.3.2广度优先遍历

广度优先遍历(BreadthFirstSearch)又称为广度优先搜索,简称BFS。

如果以之前我们找钥匙的例子来讲,运用深度优先遍历意味着要先彻底查找完一个房间再开始另一个房间的搜索。

但我们知道,钥匙放在沙发地下等犄角旮旯的可能性极低,因此我们运用新的方案:先看看钥匙是否放在各个房间的显眼位置,如果没有,再看看各个房间的抽屉有没有。这样逐步扩大查找的范围的方式我们称为广度优先遍历

7.3.3有向图的遍历代码

#include <stdio.h>
#include <malloc.h>
#define MAX 31 
int visited[MAX]; 

typedef struct ArcNode
{ 
	int adjvex;      //该弧指向的顶点的位置
	struct ArcNode *nextarc;  //指向下一条弧的指针
}ArcNode; 

typedef struct VNode
{ 
	char data;      //顶点信息
	struct ArcNode *firstarc;  //指向第一条依附该顶点的弧的指针
}VNode, AdjList[MAX]; 

typedef struct 
{ 
	AdjList adjlist;
	int vexnum, arcnum;    //图的当前顶点数和弧数
}ALGraph; 


void CreateDG(ALGraph *G);   //创建邻接表
void DispAdj(ALGraph *G);   //输出邻接表
void DFS(ALGraph *G, int v);  //深度优先遍历
void BFS(ALGraph *G, int v);  //广度优先遍历

void CreateDG(ALGraph *G)
{ 
	int i, k, j, v1, v2; 
	ArcNode *p;
	printf("输入图的顶点数和弧数:" ); 
	scanf("%d,%d", &G->vexnum, &G->arcnum); 
	printf("\n");
	
	for(i = 1; i <= G->vexnum; i++)
	{
		printf("输入第%d个顶点信息:", i);
		scanf("%s", &G->adjlist[i].data); 
	}
	printf("\n");
	for(i = 1; i <= G->vexnum; ++i) 
		G->adjlist[i].firstarc = NULL;
	for(k = 1; k <= G->arcnum; ++k)
	{ 
		printf("输入第%d条弧的弧尾和弧头:", k); 
		scanf("%d,%d", &v1, &v2); 
		i = v1;
		j = v2; 
		p = (ArcNode *)malloc(sizeof(ArcNode)); 
		p->adjvex = j; 
		p->nextarc = G->adjlist[i].firstarc; 
		G->adjlist[i].firstarc = p; 
	}
	printf("\n");

}

void DispAdj(ALGraph *G)
{ 
	int i; 
	ArcNode *q; 
	for(i = 1; i <= G->vexnum; ++i)
	{ 
		q = G->adjlist[i].firstarc; 
		printf("%d", i);
		while(q != NULL)
		{ 
			printf("->%d", q->adjvex); 
			q = q->nextarc; 
		} 
		printf("\n"); 
	} 
} 

void DFS(ALGraph *G, int v)
{
	ArcNode *p; 
	visited[v] = 1; 
	printf("%3d", v); 
	for(p = G->adjlist[v].firstarc; p != NULL; )
	{ 
		if(!visited[p->adjvex]) 
            DFS(G, p->adjvex); 
		p = p->nextarc; 
	} 
}

void BFS(ALGraph *G, int v)
{ 
	
	ArcNode *p;
	int queue[MAX], w, i;
	int front = 0, rear = 0;
	for(i = 0; i < MAX; i++)
		visited[i] = 0;
	printf("%3d", v);
	visited[v] = 1;
	rear = (rear + 1) % MAX;
	queue[rear] = v;
	while(front != rear)
	{
		front = (front + 1) % MAX;
		w = queue[front];
		p = G->adjlist[w].firstarc;
		while(p != NULL)
		{
			if(visited[p->adjvex] == 0)
			{
				printf("%3d",p->adjvex);
				visited[p->adjvex] = 1;
				rear = (rear+1) % MAX;
				queue[rear] = p->adjvex;
			}
			p = p->nextarc;
		}
	}
	printf("%\n");
}

int main()
{
	//int i, j, k; 
	ALGraph *G;
	printf("\n"); 
	G=(ALGraph *)malloc(sizeof(ALGraph)); 
	CreateDG(G); 
	printf("图的邻接表为:\n"); 
	DispAdj(G); 
	printf("\n");
	printf("从顶点1开始的深度优先搜索:\n"); 
	DFS(G, 1); 
	printf("\n");
	printf("从顶点1开始的广度优先搜索:\n"); 
	BFS(G, 1); 
	printf("\n");
	return 0;
}
  • 6
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值