数据结构总复习

文章目录

线性表

在这里插入图片描述

顺序存储

在这里插入图片描述
只列出创建和插入与删除的操作

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

  • 在操作完成,对线性表的元素进行移动的时候,用地址来实现的话,更加便捷,明了

动态分配的顺序存储结构

通过分析代码,我们发现,要注意什么:

  • 要分清你的下标
  • Insert 函数是可以用来没有元素的时候,增加元素的
  • Init(或者Create )函数一般只用来分配空间等的初始化
//动态分配空间的顺序存储结构的线性表
#include<stdio.h>
#include<stdlib.h>

#define Linitesize 100
#define Laddsize 10
#define OK 1
#define error 0

typedef int Status;
typedef int Elemtype;
typedef struct{
	Elemtype * elem;
	int length;
	int listsize;
}SqList;
void Show(SqList L)
{
	int i;
	for(i=0;i<L.length ;i++)
		printf("%d ",L.elem[i]);
	printf("\n");
	return ;
}
Status Create(SqList &L)
{
	L.elem = (Elemtype *)malloc(Linitesize*sizeof(Elemtype));
	if(!(L.elem ))
	return error;
	L.length = 0;
	L.listsize = Linitesize;
	return OK;
} 
//在第i个元素之前插入 ,从1开始计数,就是下标为i
Status Insert(SqList &L,int i,Elemtype e)
{
	
	int j;
	if(i<1||i>L.length+1 )
	return error;

	if(L.length>=L.listsize)
	{
		L.elem =(Elemtype *)realloc(L.elem ,(L.listsize + Laddsize)*sizeof(Elemtype));
		if(!(L.elem ))
		return error;
		L.listsize = L.listsize + Laddsize;
	}
	
	for(j=L.length-1 ;j>=i-1;j--)
		L.elem[j+1] = L.elem[j];
		
	L.elem[i-1] = e;
	L.length ++;
	return OK;
	
 } 
//i为你想要删除的第几个元素 
Status Delete(SqList &L,int i,Elemtype &e)
{
	int j;
	if(i<1||i>L.length )
	return error;
	e = L.elem[i-1];
	
	for(j=i-1;j<L.length-1;j++)
		L.elem[j] = L.elem[j+1];
	L.length --;
	return OK;
}

int main()
{
	int i,j;
	Elemtype e;
	SqList L;

	Create(L);

	for(i=1;i<=5;i++)
		Insert(L,i,i*i);	
	printf("输出具体数据:\n");
	Show(L);
	printf("请输入你想要删除第几个元素:\n");
	scanf("%d",&j);
	Delete(L,j,e);
	printf("删除的数据是:%d \n",e);
	Show(L);
	
	return 0;
}

 

线性表的合并(非递减)

考点

  • 两个有序递增的顺序表的合并

关键点,可以学到什么,就是分别用pa,pb,pc,来记录首地址,一句话,就是用辅助变量来方便操作

void Merge(Sqlist la,Sqlist lb,Sqlist &lc)
//目标,将原本有序递增的la,pb顺序表整合到lc ,lc认为有序递增的
{
	pa = la.elem;
	pb = la.elem;
	lc.listsize = lc.length = la.length + lb.length;
	//长度
	pc =lc.elem = (ElemType *)malloc(lc.listsize*(sizeof(ElemType)));
	if(!lc.elem)
	exit OVERFLOW;
	pa_last = pa + la.length-1;
	pb_last = pb + lb.length-1;
	while(pa<=pa_last&&pb<=pb_last)
	{
		if(*pa<*pb) *pc++ = *pa++;
		else *pc++ = *pb++;
	}
	while(pa<=pa_last) *pc++ = *pa++;
	while(pb<=pb_last) *pc++ = *pb++;
	
}

顺序表优点与缺点

  • 优点:可以随便进行数据的插入与删除
  • 优点:占据较少的空间
  • 缺点:需要连续的一串地址
  • 缺点:在插入与删除时,要移动大量的元素

链式存储

在这里插入图片描述

在这里插入图片描述

创建,插入,删除

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

  • 插入结点:寻找第i-1个结点的时候,循环要加上对于p 不为空的一个判断,循环完,要判断,循环是否正常终止,插入的时候先插后面,再接前面
    在这里插入图片描述
  • 对于链表的结点的删除,也是同理,对于寻找第i - 1 的结点的时候要注意
//动态分配空间的顺序存储结构的线性表
#include<stdio.h>
#include<stdlib.h>

#define Linitesize 100
#define Laddsize 10
#define OK 1
#define error 0

typedef int Status;
typedef int Elemtype;

typedef struct LNode{
	struct LNode * next;
	Elemtype data;
}LNode,*LinkList;
int Length(LinkList L)
{
	int sum=0;
	while(L->next !=NULL)
	{
		sum++;
		L=L->next ;
	}
	return sum;
}
//尾插法 
Status Create(LinkList &L,Elemtype e)
{
	LinkList p = L;//开始p 指向头结点 
	while(p->next !=NULL )//找到最后一个结点 
		p=p->next ;
		
	LinkList temp = (LNode *)malloc(sizeof(Elemtype));
	if(!temp) return error;
	temp->data = e;
	//由于p 指向最后一个结点,那么p->next 进行赋值,实际上会改变原来的数据
	
	temp->next = p->next ;
	p->next = temp;
	
	return OK;
	
}
Status Show(LinkList L)
{
	LinkList p = L->next  ;
	while(p !=NULL)
	{
		printf("%d ",p->data );
		p = p->next ;
	}
	printf("\n");
	return OK;
}
//在第i 个元素之前插入 ,确保不超过范围 
Status Insert(LinkList &L,int i,Elemtype e)
{
	if(i<1||i>Length(L)+1)
	return error;
	LinkList p = L;//指向头结点
	//找到第i-1个结点
	int j ;
	for(j=1;j<i;j++)
		p = p->next ; 
	
	LinkList temp = (LNode * )malloc(sizeof(LNode));
	if(!temp) return error;
	temp->data = e;
	
	temp->next = p->next ;
	p->next = temp;
	return OK;
}
Status Delete(LinkList &L,int i,Elemtype &e)
{
	//删除第i 个元素,并返回其值
	 if(i<1||i>Length(L))
	 return error;
	 //找到第i-1个结点
	 int j;
	 LinkList temp = L;
	 for(j=1;j<i;j++)
	 	temp = temp->next ;
	 e = temp->next->data;
	 temp->next = temp->next->next;
	 
	 return OK;
}
int main()
{
	Elemtype e;
	LinkList L = (LNode *)malloc(sizeof(LNode));
	L->next = NULL;
	for(int i = 1;i<= 5;i++)
		Create(L,i);
	Show(L);
	Insert(L,2,10);
	Show(L);
	Delete(L,3,e);
	Show(L);
	printf("%d \n",e);
	return 0;
	
	
}



 

在这里插入图片描述

优点以及缺点

  • 优点:插入与删除不用移动大量的元素
  • 优点:不需要连续的地址
  • 优点:采用动态链表不用固定最大长度
  • 缺点:占用较大的内存
  • 缺点:不可以随机访问某个元素

求并集,交集。差集

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

一元多项式

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

栈与队列

定义:只能在表尾进行插入与删除的线性表(先进后出)

  • 表尾是栈顶,表头是栈底

在这里插入图片描述

顺序栈

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

  • 顺序栈的精髓就是通过S.top == S.base 来判断栈是否为空
  • 用S.top-S.base >=stacksize 来判断栈是否满
  • 对于进栈操作,是*S.top = e;赋值完之后,S.top ++ ,而对于出栈操作,是 S.top -- ,再e = *S.top
#include<stdio.h>
#include<stdlib.h>

#define initsize 100
#define addsize 50
#define OK 1
#define error 0

typedef int Status;
typedef int Elemtype;

typedef struct{
	Elemtype *base,*top;//base 指向第一个元素,top 指向新的一个元素
	int stacksize;
}SqStack;
Status Init(SqStack &S)
{
	S.base =(Elemtype *)malloc(initsize*sizeof(Elemtype ));
	if(!S.base )
	return error;
	S.top = S.base ;//说明此时栈为空
	S.stacksize = initsize;
	return OK;
	 
}
Status Push(SqStack &S,Elemtype e)
{
	if(S.top -S.base >=S.stacksize )//栈满
	{
		S.base =(Elemtype *)realloc(S.base ,(initsize + addsize)*sizeof(Elemtype));
		if(!S.base)
		return error;
			
	}
	*(S.top ) = e;
	S.top ++;
	return OK;
}
Status Pop(SqStack &S,Elemtype &e)
{
	if(S.base == S.top )
	return error;//栈为空
	S.top --;
	e = *(S.top );
	return OK;
	 
}
int main()
{
	int n,i;
	Elemtype e;
	SqStack S;
	Init(S);
	printf("请输入你想要入栈的元素的个数:\n");
	scanf("%d",&n);
	for(i=1;i<=n;i++)
	{
		printf("请输入你要入栈的元素:");
		scanf("%d",&e);
		Push(S,e);
	}
	printf("出栈 \n");
	for(i=1;i<=n;i++)
	{
		Pop(S,e);
		printf("%d ",e);
	}
	
	return 0;
}

链栈

在这里插入图片描述

  • 简而言之,就是链表的插入与删除均在头结点那里操作
  • 相比之下,链栈的操作更为简单,
  • 在初始化的时候给传递过来的头指针分配一个头结点
  • 当S->next ==NULL 的时候,就说明栈为空
  • 进行插入操作的时候,先分配空间LinkStack temp ,来存储数据,temp->next =S->next; S->next=S
  • 删除操作的时候,temp = S-> next,e = temp ->data;S->next = temp ->next;
  • 不过应该注意的是,链栈是在队头进行操作,区别于顺序栈的队尾,不过,可以这么记忆,你在哪边进行插入的就在哪边进行删除就可以

#include<stdio.h>
#include<stdlib.h>

#define OK 1
#define Error 0

typedef int Status;
typedef int Elemtype;

typedef struct Node {
	Elemtype data;
	struct Node * next;
}Node,*ListStack;

Status InitStack(ListStack &S)
{
	S =(ListStack )malloc(sizeof(Node));
	if(!S)  return Error;
	S->next = NULL ;
	return OK;
	
}
Status Pop(ListStack &S,Elemtype &e)
{
	if(S->next ==NULL)//栈为空
	return Error;
	ListStack p = S->next ;
	e = p->data  ;
	S->next = p->next ;
	free(p);
	return OK; 
}

Status Push(ListStack &S,Elemtype e)
{
	ListStack p =(ListStack )malloc(sizeof(Node));
	if(!p)	return Error;
	
	p->data = e;
	p->next = S->next ;
	S->next = p;
	return OK;
}

int main()
{
	ListStack S;
	Elemtype e;
	int i; 
	InitStack(S);
	printf("请输入想要进栈的6个数据:\n");
	for(i=1;i<=6;i++)
	{
	scanf("%d",&e);
	Push(S,e);
	}
	printf("依次出栈:\n");
	for(i=1;i<=6;i++)
	{
	Pop(S,e);
	printf("%d ",e);
	}
	return 0;
	
	
	
}

栈的应用(数制的转化)

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

在这里插入图片描述

栈的应用(行编辑)

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

栈的应用(表达式求值)

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

队列

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

  • 出队列是DeQueue,进队列是EnQueue

链队列

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

  • 链队列:判断为空的条件:Q.rear==Q.front 指向头结点
  • 这也要求了,在删除操作的时候,要判断删除之后是否为空,要是为空就让两个指针相等
#include<stdio.h>
#include<stdlib.h>

#define OK 1
#define Error 0

typedef int Status ;
typedef int Elemtype;

typedef struct QNode{
	Elemtype data ;
	struct QNode * next;
	
}QNode,*QueuePtr;
typedef struct{
	QueuePtr front;
	QueuePtr rear;
}LinkQueue;

Status InitQueue(LinkQueue &Q)
{
	QNode * p=(QNode *)malloc(sizeof(QNode));//头结点 
	if(!p)	return Error;
	p->next =NULL;  
	Q.front =Q.rear =p;
	return OK;
}
Status Push(LinkQueue &Q,Elemtype e)
{
	QNode * p=(QNode *)malloc(sizeof(QNode));
	if(!p)	 return Error;

	p->data =e;
	p->next = Q.rear ->next;
	Q.rear ->next = p;
	Q.rear = p ;
	return OK;
}
Status Pop(LinkQueue &Q,Elemtype &e)
{
	if(Q.front ==Q.rear )//队列为空
	return Error;
	QNode * p ;
	p = Q.front ->next;
	e = p->data;
	Q.front ->next = p->next ;
	if(Q.rear == p)//此处十分重要,不然的话,后面释放空间的话,会出错
	Q.rear = Q.front ; 
	free(p);
	return OK;
}
int main()
{
	LinkQueue Q;
	Elemtype e;
	printf("创建带有头结点的链队列\n");
	InitQueue(Q);
	printf("请输入5个你想要依次入队列的数字\n");
	for(int i=1;i<=5;i++)
	{
		scanf("%d",&e);
		Push(Q,e);
	}
	printf("依次出队列\n");
	for(int i=1;i<=5;i++)
	{
		Pop(Q,e);
		printf("%d ",e);
	}

	return 0;
	
}


顺序队列

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

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

  • 注意循环队列的判断队列为满的方法是 队尾+1 再取模,看是不是等于 队头
  • 循环队列的插入与删除,分别是 rear + 1 取模,以及 front +1 取模

小结

在这里插入图片描述

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

串的基本操作

在这里插入图片描述

  • StrAssign 是串的赋值,StrCompare 是串的比较 ,StrLength是串的求长度,Concat是串的联接,SubString 是求子串,这5个操作是不能由其他的操作结合,属于基本的操作

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

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

在这里插入图片描述

  • 树的结点的度:拥有的子树的个数
  • 非终端结点是除了叶子结点的结点
  • 树的度为树内的各结点的度的最大值

在这里插入图片描述

二叉树

在这里插入图片描述

在这里插入图片描述

满二叉树

在这里插入图片描述

完全二叉树

在这里插入图片描述

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

二叉树的存储结构

顺序存储

在这里插入图片描述

  • 二叉树的利用下标进行数组中的对应,某个结点i 的父节点为[ i/2] ,如果存在左孩子和右孩子,则分别为 2i 与 2i + 1
  • 但是对于非二叉树,利用数组来顺序存储,没有统一的下标的标准,而且可能会浪费许多空间

链式存储

在这里插入图片描述

二叉链表(两个指针域)与三叉链表(三个指针域)

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

在这里插入图片描述

二叉树的遍历

在这里插入图片描述

先序遍历

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

  • 虽然是非递归,但是真正蕴含的思想与递归的相同,借助一个栈来对已经访问的元素进行记录,判断当前的指针p 是否为空,如果不为空,则输出结点的值,然后一路向左遍历,当到达最左边时,就出栈,得到父节点,然后对父节点进行相同的操作,当左边遍历完成,就进入右结点,进行相同的操作,当p 和 栈同时为空时遍历完成
中序遍历

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

后序遍历

在这里插入图片描述

  • 和先序遍历的非递归算法类似,都是借助一个栈,不过,不同的是,中序是从最左边的结点开始访问,当遇到指针p 为空,说明已经到达当前最左的的结点的左孩子结点,那么就可以将栈的元素出栈,输出该结点的值,然后p 就等于该结点的右孩子结点,然后又重新找最左边的元素
后序遍历

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

  • 和前序和中序的遍历相比,后序遍历的难度更大,要增加一个tag 来标记该结点是否被遍历过:首先还是先找到最左边的结点,然后判断该结点的右孩子是否存在,如果不存在,则输出该结点的数据内容,并标记tag 为1,然后让p=null(这样可以让遍历回溯到栈的元素),否则要进入又孩子结点进行新的一轮的重新的遍历:
层序遍历
Status leverTraverse(BiTree T){
	BiTree L[100] ;
	int front = 0;
	int rear = 0;
	if(T&&front==rear) //如果根节点非空,那么输出结点后存入队列 
	{
		printf("%c ",T->data);
		L[rear++] = T ;
		
	}
	while(front!=rear){
		BiTree node = L[front++]; //每次出队列 
		BiTree lc = node->lchild ; //取左孩子 
		BiTree rc = node->rchild ; //取右孩子 
		if(lc )  //如果左孩子非空,输出数据后存入队列 
		{
			printf("%c ",lc->data);
			L[rear++] = lc;
		}
		if(rc)  //如果右孩子非空,输出数据后存入队列 
		{
			printf("%c ",rc->data );
			L[rear++] = rc;
		}
	}
}

  • 所谓的层序遍历,就是借助一个队列进行相对应的遍历的操作:开始的时候,当根节点非空,输出数据之后存入队列,然后就用一个while 循环,每次从队列中取出一个结点,遍历该结点的非空的左右孩子,并存入队列中,重复该循环,直至队列被遍历完

二叉树的创建

  • 二叉链树的代码的注意事项:
  • 在进行遍历的时候,应该判断的是传入的指针是否为空,指针不为空才继续往下面进行遍历,否则的话就要返回
  • 在进行创建的时候,如果要进行字符的输入,那么就直接输入一整个序列(包含终止字符)
  • 创建的过程中,如果字符不为终止字符,则进行分配空间,存储数据,接着遍历,,若为终止字符,则应该让该指针为NULL

#include<stdio.h>
#include<stdlib.h>
#include<string.h> 
#define OK 1
#define error 0

typedef char Elemtype;
typedef int Status;

typedef struct BiTNode{
	Elemtype data;
	struct BiTNode *lchild,*rchild;
}BiTNode,*BiTree;

Status Create(BiTree &T)
{
	char temp;
	scanf("%c",&temp);
	if(temp !='!')
	{
		T = (BiTNode *)malloc(sizeof(BiTNode));
		if(!T)
		return error;
		T->data = temp ;
		Create(T->lchild );
		Create(T->rchild );
	}
	else 
	T = NULL;  //很重要
	
	return OK;
}
Status preOrder(BiTree T)
{
	if(T)//不为空才进行输出
	{
	printf("%c ",T->data );
	preOrder(T->lchild );
	preOrder(T->rchild );
	}
	return OK;
}
int main()
{
	BiTree T;
	printf("请按照先序排列进行建树(! 表示结束)\n");
	Create(T);
	preOrder(T);
	return 0;
}

注意
在这里插入图片描述

相关题型

给出二叉树的树状图,求遍历的顺序

给出中序+ 先序 或者中序+后序,求树状图

在这里插入图片描述

  • 无论是给出先序还是后序,由于分别是根左右,左右根,根结点都是在一边的,先序就是从左到右依次获取每一个根,后序就是从右到左,依次获取一个根,然后一一对照中序遍历的序列,得出哪些没有安排位置的结点是该根节点的左孩子还是右孩子即可
  • 当然,最好的方法就是,写完之后,检验一遍

线索二叉树

在这里插入图片描述

  • 如果一个结点有左孩子,那么LTag = 0,lchild 就指向该结点的左孩子,否则LTag = 1 ,lchild 指向该结点的前驱,RTag 同理

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

树,森林与二叉树的转换

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

  • 普通的树变成二叉树,就是用孩子兄弟表示法,孩子往左边走,兄弟右边走,然后森林变成二叉树,就是把森林里面的全部的树先变成二叉树,然后让另一棵二叉树为一棵二叉树的又子树,依次来构成

树与森林的遍历

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

赫夫曼树

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

  • 学会这个过程,考试可能会考
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

统计二叉树中叶子结点的个数

在这里插入图片描述

  • 开始误以为要将计数的步骤放在if(T) 的外部,但是这犯了一个问题,导致不能合理的进行递归的停止,当你放在里面的时候,就算到达的叶子结点,然后将叶子结点的左右孩子(虽然为空)进行继续递归,但是有着if(T) 进行判断,程序也能很好的完成停止的步骤

求二叉树的深度

在这里插入图片描述

  • 类似于递推,有点汉诺塔的味道

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

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

  • 注意,无向图的连通分量在物理结构上可以容易看出,但是有向图的强连通分量不能一下子就看出,要判断是否符合一个强连通图的一个定义
  • (1)以顶点v 为头的弧的数目称为v 的入度,以v 为尾的弧的数目称为v 的出度
  • (2)连通图就是任意的两个顶点,都存在路径相通,连通分量就算一个无向图中极大连通子图的数目
  • (3)在有向图中,如果对于任意一对顶点都有路径,那么称为强连通图,在有向图中,极大的强连通子图的个数被称为强连通分量

图的存储结构

在这里插入图片描述

数组表示

在这里插入图片描述

在这里插入图片描述

  • 要记住:数组表示的结构的专有的名称:ArcCell 来定义邻接矩阵结点类型,对于邻接矩阵的结点存储的值用VRType来表示,AdjMatrix 来定义整个邻接矩阵 ,arcs 用来表示 邻接矩阵的名字,vertexType 表示 顶点向量,然后vexs 就是顶点向量的名字,然后又有vexnum 和arcnum 分别记录顶点的个数以及边的个数
  • 对于图的注意事项:
  • 要记录好图的顶点的个数,边的个数,即vexnum 以及arcnum
  • 定义一个一维数组来记录数据的数据向量,一个二维的数组来记录数据的邻接矩阵
#include<stdio.h>
#include<stdlib.h>

#define OK 1
#define error 0
#define infinity 1000000
#define Max 100
 
typedef int Status;
typedef int vertexType;//这里设置的是结点的数据的类型
typedef int adjMatrix[Max][Max];//这里设置邻接矩阵 

typedef struct {
	int vexnum;//顶点的个数 
	int arcnum;//边的个数 
	vertexType vexs[Max];//顶点向量
	adjMatrix arcs ;//邻接矩阵 
	
}MGraph;

Status Create(MGraph &G)
{
	int i,j,start,end,weight;
	printf("请输入你的有向图的顶点的个数以及边的个数:\n");
	scanf("%d%d",&G.vexnum ,&G.arcnum );
	for(i=0;i<G.vexnum ;i++)
		for(j=0;j<G.vexnum ;j++)
			G.arcs[i][j] = infinity;
	printf("请输入你的个个顶点的数据\n");
	for(i=0;i<G.vexnum ;i++)
		scanf("%d",&G.vexs[i]);
	printf("请输入每条边之间的权重(起点->终点)+权重\n");	
	for(i=1;i<=G.arcnum ;i++)
	{
		scanf("%d%d%d",&start,&end,&weight);
		G.arcs[start][end] = weight;
	}
	return OK;
	
}
Status Show(MGraph G)
{
	int i,j;
	printf("个个结点的数据分别为\n");
	for(i=0;i<G.vexnum ;i++)
		printf("%d ",G.vexs[i]);
	printf("\n");
	printf("邻接矩阵:\n");
	for(i=0;i<G.vexnum  ;i++)
	{
		for(j=0;j<G.vexnum  ;j++)
		printf("%d  ",G.arcs[i][j]);
		printf("\n");
	}
	return OK;
}
int main()
{
	MGraph G;
	Create(G);
	Show(G);
	return 0;
		
}



缺点
在这里插入图片描述

  • 1.对顶点的位置的修改麻烦,涉及顶点向量与邻接矩阵的同时的修改,2.浪费空间,开辟的空间按照最大的需求来开3.对邻接矩阵的检测麻烦,每一个都要检查

邻接表

在这里插入图片描述

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

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

  • 记住表结点,ArcNode存储边,以及一个ArcNode 指针,然后头结点这里,就VexType 类型 来定义data,还有 一个ArcNode 的一个指针,最后在结构体外部定义一个AdjList 的顶点向量,然后有vexnum 和arcnum 来记录个数,最后是ALGraph

在这里插入图片描述

  • 对于邻接表,找到某一个顶点的邻接点,就直接遍历邻接表即可,但是判断某两个顶点是否有临界边就要遍历,不及邻接矩阵直接判断
#include<stdio.h>
#include<stdlib.h>

#define OK 1
#define error 0
#define max 100
typedef int VertexType;

typedef int Status;
//表结点 
typedef struct ArcNode{
	int adjvex;//记录边的另一个顶点
	int weight; 
	struct ArcNode *nextarc;	 
}ArcNode;
//头结点 
typedef struct VNode{
	VertexType data;
	ArcNode * firstarc;
	
}VNode,AdjList[max];

typedef struct{
	int arcnum;//边的个数 
	int vexnum;//顶点的个数
	AdjList vertices;//头结点向量 
}ALGraph;
//创造有向图 
Status Create(ALGraph &G)
{
	int i,j;
	int start,end,weight;
	printf("请输入有向图的顶点的个数以及边的个数:\n");
	scanf("%d%d",&G.vexnum ,&G.arcnum );
	printf("请输入每个数据结点的数据:\n");
	for(i=0;i<G.vexnum ;i++)
	{
		scanf("%d",&G.vertices[i].data );
		G.vertices[i].firstarc = NULL;
	}
	printf("请输入每条边的数据(起点 -> 终点 + 权重)\n");
	for(i=0;i<G.arcnum ;i++)
	{
		scanf("%d%d%d",&start,&end,&weight);
		ArcNode *temp = (ArcNode *)malloc(sizeof(ArcNode));//分配表结点的空间 
		temp->adjvex = end;
		temp->weight = weight;
		temp->nextarc = G.vertices[start].firstarc ;//衔接在头结点旁边 
		G.vertices[start].firstarc = temp;
		
	}
	return OK;
	
}
Status Show(ALGraph G)
{
	int i;
	for(i=0;i<G.vexnum ;i++)
	{
		printf("%d :",G.vertices[i].data );
		ArcNode *p = G.vertices[i].firstarc ;
		while(p)
		{
			printf("%d 权重(%d)",p->adjvex ,p->weight );
			p=p->nextarc ;
		}
		printf("\n");
	}
	
	return OK;
}
int main()
{
	ALGraph G;
	Create(G);
	Show(G);
	
	return 0;
	
}

逆邻接表

在这里插入图片描述

  • 逆邻接表就是由尾指向头(接受),而邻接表是由头指向尾(付出)

十字链表

在这里插入图片描述

  • tailvex 和 headvex 分别为一条边的尾和头,hlink 指向与弧头相同的下一条弧,tlink 指向弧尾相同的下一条弧
  • 顶点结点的firstin 与 firstout 分别指向以该顶点为弧头和弧尾的弧
  • 一定要分清楚头和尾,最好按照上面定义的顺序 tailvex ,headvex,hlink,tlink 这样的话,容易看
  • 容易求入度和出度

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

邻接多重表(用于无向图与无向网)

在这里插入图片描述

  • 邻接多重表与十字链表的区别:顶点结点不一样,邻接多重表只有一个data 和一个firstedge ,而十字链表有data ,firstin,firstou ,对于表结点,形式上,十字链表有tailvex,headvex,hlink,tlink和一个info ,但是邻接多重表多了一个mark 来记录有没有被访问过,但是实际上hlink ,tlink 分别指向以headvex 为头的结点,以tailvex 为尾的结点,但是ilink 和jlink 是分别指向下一个以 i,j相邻的边,并不考虑头和尾(十字链表用来表示有向图,多重表来表示无向图)
  • 邻接多重表与邻接表(无向图和有向图都可以)的区别:邻接表的表头只有一个data 和 firstarc,并且表结点只有记录弧头的一个数据和一个nextarc 指针

在这里插入图片描述

图的遍历

在这里插入图片描述

深度优先搜索

在这里插入图片描述

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

  • 深度优先的算法,采用数组的存储结构,就是n^2 的时间复杂度,邻接表的话就是n+e 的复杂度,在总的函数中没有区别,就是要遍历整个顶点,对于DFS 函数,区别就在于,由于要判断一个结点的邻接顶点,对于数组表示法,要遍历全部结点才可以找到,但是对于邻接表表示法的话,就不用,直接遍历该结点的邻接表即可,总的遍历才为边数e
  • 对于DFS 的具体的实现,使用数组法来判断两个顶点之间是否有路径比较块,也就是G.arc[i][j] !=0

广度优先搜索

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

  • 与树的层序遍历相似,都是借助一个队列:外部是一个大循环,用来遍历全部的结点,当该结点没有被访问时,就对该结点进行访问,然后加入队列中,然后对队列里面的元素进行访问,如果队列不为空,则队头出队列,对队头的邻接的且没有被访问的顶点进行访问,访问完成就加入队列
  • 时间复杂度与深度优先相同
  • 可以用来计算连通分支的个数

图的连通分支的问题

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

  • 对于图的连通分支问题,只需记录在对每个结点的大循环中,if 语句进行几次即可

连通图的最小代价生成树

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

普里姆算法

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

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

  • 对于普里姆算法,为什么时间的复杂度是n^2? 以顶点为基础,每次在剩余的顶点中找与已经找到的顶点的最小的代价,并加入该顶点
克鲁斯卡尔算法

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

  • 克鲁斯卡尔算法,就是先将所有的顶点都加进去,然后从边的权值从小到大开始筛选,当新增减的边的两端的顶点位于不同的连通分量就加入该条边,整体的时间复杂度就是 eloge

最短路径

在这里插入图片描述

在这里插入图片描述

迪杰斯特拉算法

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

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

弗洛伊德算法

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

有向无环图的应用

拓扑排序

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

关键路径

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

  • 就是说先从前往后,事件Vj 的最早的发生的时间,其实是到达该点的最晚的时间,从源点开始算起,算到最后一个汇点,然后以汇点为开始,往前面算,每件事件最晚的发生时间,这时取得是最小值,当Ve(i)=Vl(i) 时,说明该事件是关键事件

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

有向图得可达问题

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

  • 算法得精髓:判断i到j 是否可达,就是判断i 等不等于j ,若等于,直接成立,否则,判断是否存在 i到达 k,然后递归判断k 到j 是否可达,将问题缩小化

图的中心顶点问题

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

查找

在这里插入图片描述

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

顺序表的查找

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

有序表的查找

折半查找法

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

  • 注意,最多的查找次数不多于[ logn]+1,可以看作二叉树的深度

索引顺序表的查找

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

动态查找表

二叉排序树

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

  • 类似于折半查找
二叉树的生成

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

二叉排序树的删除

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

  • 就是在左子树找一个最大的,或者在右子树找一个最小的,顶替即可

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

二叉排序树的查找

在这里插入图片描述

平衡二叉树

在这里插入图片描述

  • 个人觉得是为了增加查找的速度,最好的打算是logn

平衡二叉树的调整

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

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

  • LR 型,就是c 先和b进行左旋转,然后c 再和a 进行右旋转

在这里插入图片描述

  • RL 型,就是c 先和b进行右旋转,然后c 再和a 进行左旋转

在这里插入图片描述

在这里插入图片描述

平衡二叉树的插入

在这里插入图片描述

B-树

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

B- 树的插入与删除

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

  • 记得m 阶B- 树的每一个结点都至少[m/2]棵子树,对于插入与删除的话,没有什么特别的算法,就是要保证是B- 二叉树的结构,也就是对于Ki-1<Ki 的,且Ai-1指向的结点的最大值小于Ki,Ai指向的结点的最小值大于Ki
  • 叶子结点都是空

B+ 树

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

  • 注意,B+ 树的值都存储在叶子结点,而B- 树的叶子结点不存储信息;B- 树的数据的特点,但是B+ 树的非终端结点的数据只是子树的最大值或者最小值

散列(哈希表)

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

哈希函数的构造方法

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

解决冲突

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

  • 如何理解:查找成功的时候的计算?先按照哈希函数来计算,若找不到,就按照开放地址法来查找,将每一个数字的查找次数加起来即可
  • 如何理解:查找不成功?按照给定的已经排好的哈希表,进行m次的不成功的查找,当按照解决冲突的方法继续查找到一个为空的值后就可以暂停,然后继续找下一个,最后全部加起来,除以总数m

在这里插入图片描述

  • 对于链地址法的查找成功的平均查找长度更好算:第一列的个数乘1+第二列的个数乘2++++即可

哈希表的查找

在这里插入图片描述

排序

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

插入排序算法

在这里插入图片描述

直接插入排序

在这里插入图片描述

在这里插入图片描述


//直接插入算法的升序排列 
void InsertSort(SqList &L)
{
	int i,j;
	for(j=2;j<=L.length;j++)
	{
		//降序排序的话,就将<改成> 
		if(L.elem[j].key<L.elem[j-1].key)
		{
			L.elem[0] = L.elem[j];
			//及时更新,将j-1的数据存到 j 处 
			L.elem[j] = L.elem[j-1];
			for(i=j-2;L.elem[0].key < L.elem[i];i--)
				L.elem[i+1] = L.elem[i];
			//当跳出循环的时候,说明L.elem[0].key 比L.elem[i].key 大
			//那么 L.elem[0].key 应该插在L.elem[i].key 后面
			L.elem[i+1] =  L.elem[0];
		}
	}
}

在这里插入图片描述

其他插入排序

在这里插入图片描述

折半插入排序

在这里插入图片描述


void BinsertSort(SqList &L)
{
	// 外循环,从第二个元素开始,逐个将元素插入到已排序序列中
	int i,j;
	for(i=2;i<=L.length;i++)
	{
		// 将当前元素保存到临时变量中
		L.elem[0] = L.elem[i];
		// 初始化已排序序列的左、右边界
		int low = 1,high = i-1;
		// 使用二分查找算法,找到当前元素的插入位置
		while(low<=high)
		{
			// 计算中间元素的下标
			int m = (low + high)/2;
			// 如果当前元素小于中间元素,则将右边界向左移动
			if(L.elem[0].key < L.elem[m].key) high = m-1;
			// 否则,将左边界向右移动
			else low = m+1;	
		}
		// 将已排序序列中大于当前元素的元素向后移动一位
		for(j=i-1;j>= high+1;j--)
			L.elem[j+1] = L.elem[j];
		// 将当前元素插入到已排序序列中
		L.elem[high + 1] = L.elem[0];
	}
}


  • 如何理解?在找到i 的插入的位置的时候,结束的条件一定是high = low -1 ,而由于这样的计算肯定使得 L.r[high].key <=L.r[0].key 的,所以要插入high +1 之中,先将原本的high + 1到 i- 1 的元素向后移动一位,最后将元素插入high + 1

2-路插入排序

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

表插入排序

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

在这里插入图片描述

希尔排序

在这里插入图片描述

  • 注意:当序列基本有序的时候,希尔排序十分高效
    在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

交换排序

在这里插入图片描述

冒泡排序

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

在这里插入图片描述

快速排序

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

在这里插入图片描述

  • 就是利用了一个类似于树的遍历,得到枢纽的位置之后,再将左边和右边部分调用相应的函数(递归)

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

  • 枢纽的选择最好是中间的位置

选择排序

在这里插入图片描述

简单选择排序

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

树型选择排序

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

  • 每次从底部开始每两个进行选择,由于是正则的二叉树,所以可以比较,依次向上传递,每次比较完成的元素用无穷来代替

堆排序

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

  • 如何进行建堆操作:先按照顺序建立完全二叉树,然后从第一个非终端结点开始逐步调整为大根堆还是小根堆,就是调整每一个都符合就可以
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  • 堆排序要时刻更新列表(按照完全二叉树的顺序),每一次建堆成功,都要将队列的最前面与最后面进行交换,然后再进行重新的建堆操作
    在这里插入图片描述

归并排序

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

基数排序

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

数组与广义表

在这里插入图片描述

对于稀疏矩阵的存储结构,一般有三元组顺序存储以及链表存储

三元组顺序存储

在这里插入图片描述

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

转置算法

算法一:
在这里插入图片描述

  • 总体的思想就是:从列进行遍历,内循环用三元组的非0的个数tu 来控制即可,每次遇到匹配列的元素就进行转置,存入新的三元组T 中

算法二:
在这里插入图片描述
在这里插入图片描述

矩阵加法

在这里插入图片描述

主要的理念就是线性表的并集运算

链式存储

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

广义表

在这里插入图片描述

注意:表头不一定是广义表,但是表尾一定是广义表

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

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

  • 如何判定自己画的广义表是否错误?
  • 原子都用原子结点来存储,就是说你的元素应该都是从头指针引出
  • 其中表姐点中,除了第一个tag = 1,其余的hp 、 tp 分别为头指针与尾指针
  • 对于存储结构广义表的转换是一个难点

广义表的存储结构的画法的转换

在这里插入图片描述

常用存储结构

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

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值