数据结构与算法(一)

第一章 数据结构概述

数据:是描述客观事物的符号,是计算机中可以操作的对象,是能被计算机识别,并输人给计算机处理的符号集合。数据不仅仅包括整型、实型等数值类型,还包括字符及声音、图像、视频等非数值类型。

数据元素:是组成数据的、有一定意义的基本单位,在计算机中通常作为整体处理。也被称为记录。

数据项:一个数据元素可以由若干个数据项组成

数据对象:是性质相同的数据元素的集合,是数据的子集 。

数据结构:是相互之间存在一种或多种特定关系的数据元素的集合。

逻辑结构:是指数据对象中数据元素之间的相互关系。其实这也是我们今后最需要关注的问题。逻辑结构分为以下四种;
1.集合结构
集合结构:集合结构中的数据元素除了同属于一个集合外,它们之间没有其他关系。
2.线性结构
线性结构:线性结构中的数据元素之间是一对一的关系。
3.树形结构
树形结构:树形结构中的数据元素之间存在一种一对多的层次关系。
4.图形结构
图形结构:图形结构的数据元素是多对多的关系。

物理结构:是指数据的逻辑结构在计算机中的存储形式。数据元素的存储结构形式有两种:顺序存储和链式存储。
1.顺序存储结构
顺序存储结构:是把数据元素存放在地址连续的存储单元里,其数据间的逻辑关系和物理关系是一致的。
2.链式存储结构
链式存储结构:是把数据元素存放在任意的存储单元里,这组存储单元可以是连续的,也可以是不连续的。数据元素的存储关系并不能反映其逻辑关系,因此需要用一个指针存放数据元素的地址,这样通过地址就可以找到相关联数据元素的位置。

抽象数据类型(Abstract Data Type,ADT):是指一个数学模型及定义在该模型上的一组操作。抽象数据类型的定义仅取决于它的一组逻辑特性,而与其在计算机内部如何表示和实现无关。

第二章 算法

算法是解决特定问题求解步骤的描述,在计算机中表现为指令的有限序列,并且每条指令表示一个或多个操作。

算法的特性
算法具有五个基本特性:输入、输出、有穷性、确定性和可行性。
1.输入、输出
输入和输出特性比较容易理解,算法具有零个或多个输入。
2.有穷性
有穷性:指算法在执行有限的步骤之后,自动结束而不会出现无限循环,并且每一个步骤在可接受的时间内完成。
3.确定性
确定性:算法的每一步骤都具有确定的含义,不会出现二义性。算法在一定条件下,只有一条执行路径,相同的输人只能有唯一的输出结果。算法的每个步骤被精确定义而无歧义。
4.可行性
可行性:算法的每一步都必须是可行的,也就是说,每一步都能够通过执行有限次数完成。

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

常数阶:O(1)
线性阶:O(n)

for(int i=0;i<n;i++)

平方阶😮(n的几次方)

for(int i=0;i<n;i++){
  for(int j=i;j<n;j++){
  }
}

对数阶:O(以什么为底n的对数)

int count = 0;
while(count<n){
count = count*2;
}

时间复杂度比较
在这里插入图片描述
第三章 线性表

线性表:零个或多个数据元素的有限序列。元素之间是有顺序的,若元素存在多个,则第一个元素无前驱,最后一个元素无后继,其他每个元素都有且只有一个前驱和后继。

3.1顺序线性表

线性表的顺序存储结构,指的是用一段地址连续的存储单元依次存储线性表的数据元素。

线性表的顺序存储的结构代码:

#define MAXSIZE 20
typedef int ElemType;
typedef struct{
	ElemType data[MAXSIZE];
	int length;
}SqList;

获得元素操作:

#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 1
typedef int Status;

Status GetElem(SqList L,int i,ElemType *e){
	if(L.length==0||i<1||i>L.length){
		return ERROR;
	}
	*e = L.data[i-1];
	return OK;
}

在第i个位置插入元素:

插人算法的思路:
如果插人位置不合理,抛出异常;
如果线性表长度大于等于数组长度,则抛出异常或动态增加容量;
从最后一个元素开始向前遍历到第i个位置,分别将它们都向后移动一个位置;
将要插人元素填人位置i处;
表长加1。

Status ListInsert(SqList *L,int i,ElemType e){
	int k;
	if(L->length==MAXSIZE)//满了
		return ERROR;
	if(i<1||i>L->length+1)//不在范围内
		return ERROR;
	if(i<=L->length){
		for(k=L->length-1;k>=i-1;k--)
		L->data[k+1]=L->data[k];
	}//不在表尾
	L->data[i-1]=e;
	L->length++;
	return OK;
}

删除第i个元素:

Status ListDelete(SqList *L,int i,ElemType *e){
	int k;
	if(L->length==0)//为空
		return ERROR;
	if(i<1||i>L->length)//位置不对
		return ERROR;
	*e=L->data[i-1];
	if(i<L->length){//不在尾部
		for(k=i;k<L->length;k++)
		L->data[k-1]=L->data[k];
	}
	L->length--;
	retrun OK;
}

优点:
无须为表中元素之间的逻辑关系而增加额外的存储空间;
可以快速查询到相应位置元素。
缺点:
删除增加元素需要移动大量元素;
长度变化较大时,难以确定存储空间的容量;
造成存储空间的“碎片”。

3.2链式线性表

线性表的链式存储结构的特点是用一组任意的存储单元存储线性表的数据元素,这组存储单元可以是连续的,也可以是不连续的。这就意味着,这些数据元素可以存在内存未被占用的任意存储位置。以前在顺序结构中,每个数据元素只需要存数据元素信息就可以了。现在链式结构中,除了要存数据元素信息外,还要存储它的后继元素的存储地址。

3.2.1 单链表
线性表单链表存储结构定义:

typedef struct Node{
	ElemType data;
	struct Node *next;
}Node;
typedef struct Node *LinkList;

单链表的读取:
1.声明一个结点p指向链表第一个结点,初始化j从1开始;
2.当j<i时,就遍历链表,让p的指针向后移动,不断指向下一结点,j累加1,
3.若到链表末尾p为空,则说明第i个元素不存在;
4.否则查找成功,返回结点p的数据。
实现代码算法如下:

Status GetElem(LinkList L,int i,ElemType *e){
	int j;
	LinkList p;
	p=L->next;
	j=1;
	while(p&&j<i){
		p=p->next;
		j++;
	}
	if(!p||j>i)
		return ERROR;
	*e=p->data;
	return OK;
}

单链表的插入:
1.声明一结点p指向链表第一个结点,初始化j从1开始;
2.当j<i时,就遍历链表,让p的指针向后移动,不断指向下一结点,j累加1;
3.若到链表末尾p为空,则说明第i个元素不存在;
4.否则查找成功,在系统中生成一个空结点s; 将数据元素e赋值给s->data;
6.单链表的插人标准语句s->next=p->next; p->next=s;
7.返回成功。
实现代码算法如下:

Status ListInsert(LinkList *L,int i,ElemType e){
	int j;
	LinkList p,s;
	p = *L;
	j = 1;
	while(p&&j<i){
		p = p->next;
		j++;
	}
	if(!p||j>i)
		return ERROR;
	s = (LinkList)malloc(sizeof(Node));
	s->data = e;
	s->next = p->next;
	p->next = s;
	return OK;
}

单链表的删除:
1.声明一结点p指向链表第一个结点,初始化j从1开始;
2,当j<i时,就遍历链表,让p的指针向后移动,不断指向下一个结点,j累加;
3,若到链表末尾p为空,则说明第i个元素不存在;
4.否则查找成功,将欲删除的结点p->next赋值给q;
5,单链表的删除标准语句p->next=q->next;
6,将q结点中的数据赋值给e,作为返回;
7,释放q结点;
8,返回成功。
实现代码算法如下:

Status ListDelete(LinkList *L,int i,ElemType *e){
	int j;
	LinkList p,q;
	p = *L;
	j = 1;
	while((p->next)&&j<i){
		p = p->next;
		j++;
	}
	if(!(p->next)||j>i)
		return ERROR;
	q = p->next;
	p->next = q->next;
	*e = q->data;
	free(q);
	return OK;
}

单链表的整表创建:

头插法:
1,声明一结点p和计数器变量i;
2.初始化一空链表L,
3.让L的头结点的指针指向NULL,即建立一个带头结点的单链表;
4.循环:
生成一新结点赋值给p;
随机生成一数字赋值给p的数据域p->data;
将p插人到头结点与前一新结点之间。
实现代码算法如下:

void CreateListHead(LinkList *L,int n){
	LinkList p;
	int i;
	srand(time(0));//初始化随机数种子
	*L = (LinkList)malloc(sizeof(Node));
	(*L)->next = null;
	for(i=0;i<n;i++){
		p = (LinkList)malloc(sizeof(Node));
		p->data = rand()%100+1;
		p->next = (*L)->next;
		(*L)->next = p;
	}
}

尾插法:

void CreateListTail(LinkList *L,int n){
	LinkList p,r;
	int i;
	srand(time(0));
	*L = (LinkList)malloc(sizeof(Node));
	r = *L;
	for(i=0;i<n;i++){
		p = (LinkList)malloc(sizeof(Node));
		p->data = rand()%100+1;
		r->next = p;
		r = p;
	}
	r->next = null;
}

单链表的整表删除:
1.声明一结点p和q;
2.将第一个结点赋值给p;
3.循环:
将下一结点赋值给q;
释放p;
将q赋值给p。
实现代码如下:

Status ClearList(LinkList *L){
	LinkList p,q;
	p = (*L)->next;
	while(p){
		q = p->next;
		free(p);
		p = q;
 	}
 	(*L)->next = null;
 	return OK;
}

3.2.2 静态链表
我们把这种用数组描述的链表叫做静态链表,这种描述方法还有起名叫做游标实现法。
定义:

#define MAXSIZE 1000
typedef struct{
	ElemType data;
	int cur;
}Component,StaticLinkList[MAXSIZE];

另外我们对数组第一个和最后一个元素作为特殊元素处理,不存数据。我们通常把未被使用的数组元素称为备用链表。而数组第一个元素,即下标为0的元素的cur 就存放备用链表的第一个结点的下标;而数组的最后一个元素的cur则存放第一个有数值的元素的下标,相当于单链表中的头结点作用,当整个链表为空时,则为0。
初始化:

Status InitList(StaticLinkList space){
	int i;
	for(i=0;i<MAXSIZE-1;i++)
		space[i].cur = i+1;
	space[MAXSIZE-1].cur = 0;
	return OK;
}

3.2.3 循环链表
将单链表中终端结点的指针端由空指针改为指向头结点,就使整个单链表形成一个环,这种头尾相接的单链表称为单循环链表,简称循环链表(circuhr linkedList)。

3.2.4 双向链表
双向链表(linked list)是在单链表的每个结点中,再设置一个指向其前驱结点的指针域。所以在双向链表中的结点都有两个指针域,一个指向直接后继,另一个指向直接前驱。

第四章 栈和队列
4.1
栈(stack)是限定仅在表尾进行插入和删除操作的线性表。
我们把允许插人和删除的一端称为栈顶(top) ,另一端称为栈底(bo№m),不含任何数据元素的栈称为空栈。栈又称为后进先出(Last № First0ut)的线性表,简称LIFO结构。
首先它是一个线性表,也就是说,栈元素具有线性关系,即前驱后继关系。只不过它是一种特殊的线性表而已。定义中说是在线性表的表尾进行插入和删除操作,这里表尾是指栈顶,而不是栈底。
栈的插人操作,叫作进栈,也称压栈、入栈。栈的删除操作,叫作出栈。

4.1.1 栈的顺序存储结构
定义:

typedef int SElemType
#define MAXSIZE 5;
typedef{
	SElemType data[MAXSIZE];
	int top;
}SqStack;

进栈操作:

Status push(SqStack *S,SElemType e){
	if(S->top == MAXSIZE-1)
		return ERROR;
	S->top++;
	S->data[S->top]=e;
	return OK;
}

出栈操作:

Status Pop(SqStack *S,SElemType *e){
	if(S->top==-1)
		return ERROR;
	*e = S->data[S->top];
	S->top--;
	return OK;
}

4.1.2 两栈共享空间
数组有两个端点,两个栈有两个栈底,让一个栈的栈底为数组的始端,即下标为0处,另一个栈为栈的末端,即下标为数组长度n-1处。这样,两个栈如果增加元素,就是两端点向中间延伸。若栈顶指针分别为top1和top2,栈满top1+1=top2。
定义:

typedef struct{
	SElemType data[MAXSIZE];
	int top1;
	int top2;
}SqDoubleStack;

插入元素:

Status push(SqDoubleStack *S,SElemType e,int stackNumber){
	if(S->top1+1==S->top2)
		return ERROR;
	if(stackNumber==1)
		S->data[++S->top1]=e;
	else if(stackNumber==2)
		S->data[--S->top2]=e;
	return OK;
}

pop操作:

Status pop(SqDoubleStack *S,SElemType e,int stackNumber){
	if(stackNumber==1){
		if(S->top1==-1)
			return ERROR;
		*e=S->data[S->top1--];
	}else if(stackNumber==2){
		if(S->top2==MAXSIZE)
			return ERROR;
		*e=S->data[S->top2++];
	}
	return OK;
}

4.1.3 栈的链式存储结构(链栈)
定义:

typedef struct{
	SElemType data;
	struct StackNode *next;
}stackNode,*LinkStackPtr;
typedef struct{
	LinkStackPtr top;
	int count;
}LinkStack;

链栈进栈操作:

Status push(LinkStack *S,SElemType e){
	LinkStackPtr s = (LinkStackPtr)malloc(sizeof(StackNode));
	s->data=e;
	s->next=S->top;
	S->top=s;
	S->count++;
	return OK;
}

链栈的出栈操作:

Status pop(LinkStack *S,SElemType *e){
	LinkStackPtr p;
	if(StackEmpty(*S))
		return ERROR;
	*e=S->top->data;
	p = S->top;
	S->top=p->next;
	free(p);
	S->count--;
	return OK:
}

int StackEmpty(LinkStack S){
	if(S.count==0)
		return 1;
	else
		return 0;
}

栈的作用:递归,递归后续在讲解。

4.2队列

队列(queue)是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。

队列的顺序存储结构实现与顺序表一致。

4.2.1 循环队列
我们把队列的这种头尾相接的顺序存储结构称为循环队列。

定义:

typedef int QElemType;

typedef struct{
	QElemType data[MAXSIZE];
	int front;
	int rear;
}SqQueue;

循环队列初始化:

Status InitQueue(SqQueue *Q){
	Q->front=0;
	Q->rear=0;
	return OK;
}

循环队列求队列长度代码如下:

int QueueLength(SqQueue Q){
	return (Q.rear-Q.front+MAXSIZE)%MAXSIZE;
}

循环队列入队操作:

Status EnQueue(SqQueue *Q,QElemType e){
	if((Q->rear+1)%MAXSIZE==Q->front)
		return ERROR;
	Q->data[Q->rear]=e;
	Q->rear=(Q->rear+1)%MAXSIZE;
	return OK;
}

循环队列出队操作:

Status DeQueue(SqQueue *Q,QElemType *e){
	if(Q->rear==Q->front)
		return ERROR;
	*e=Q->data[Q->front];
	Q->front=(Q->front+1)%MAXSIZE;
	return OK;
}

4.2.2 链队列
队列的链式存储结构,其实就是线性表的单链表,只不过它只能尾进头出而已, 我们把它简称为链队列。为了操作上的方便,我们将队头指针指向链队列的头结点, 而队尾指针指向终端结点。

定义:

typedef int QElemType;

typedef struct{
	QElemType data;
	struct QNode *next;
}QNode,*QueuePtr;

typedef struct{
	QueuePtr front,rear;
}LinkQueue;

入队操作:

Status EnQueue(LinkQueue *Q,QElemType e){
	QueuePtr s=(QueuePtr)malloc(sizeof(QNode));
	s->data=e;
	s->next=NULL:
	Q->rear->next=s;
	Q->rear=s;
	return OK;
}

出队操作:

Status DeQueue(LinkQueue *Q,QElemType *e){
	QueuePtr p;
	if(Q->rear==Q->front)
		return ERROR;
	p=Q->front->next;
	*e=p->data;
	Q->front->next=p->next;
	if(Q->rear==p)
		Q->rear=Q->front;
	free(p);
	retrun OK;
	
}

第五章 串

串(string)是由零个或多个字符组成的有限序列,又名叫字符串。

5.1KMP模式匹配算法

第六章 树

6.1 基本概念
树(Tree)是n(n >=0)个结点的有限集。n=0时称为空树。在任意一棵非空树中:(1)有且仅有一个特定的称为根(Root)的结点;(2)当n > 1时,其余结点可分为m(m > 0)个互不相交的有限集T1、T2、 、Tm,其中每一个集合本身又是一棵树,并且称为根的子树(SubTree )。

森林(Forest)是m (m>0)棵互不相交的树的集合。对树中每个结点而言,其子树的集合即为森林。

:树内各结点的度的最大值。
深度或高度:树中结点的最大层次成为数的深度或高度。

双亲表示法:我们假设以一组连续空间存储树的结点,同时在每个结点中,附设一个指示器指示其双亲结点到链表中的位置。也就是说,每个结点除了知道自己是谁以外,还知道它的双亲在哪里。

孩子表示法:具体办法是,把每个结点的孩子结点排列起来,以单链表作存储结构,则n个结点有n个孩子链表,如果是叶子结点则此单链表为空。然后n个头指针又组成一个线性表,采用顺序存储结构,存放进一个一维数组中。

孩子兄弟表示法:我们设置两个指针,分别指向该结点的第一个孩子和此结点的右兄弟。

6.2二叉树

二叉树(Binary Tree)总n(n >=0)个结点的有限集合,该集合 或者为空集(称为空二叉树),或者由一个根结点和两棵互不相交的、分别称为根结点的左子树和右子树的二叉树组成。

斜树:顾名思义,斜树一定要是斜的,但是往哪斜还是有讲究。所有的结点都只有左子树的二叉树叫左斜树。所有结点都是只有右子树的二叉树叫右斜树。这两者统称为斜树。

满二叉树:在一棵二叉树中,如果所有分支结点都存在左子树和右子树,并且所有叶子都在同一层上,这样的二叉树称为满二叉树 。

完全二叉树 :对一棵具有n个结点的二叉树按层序编号,如果编号为i (1<i< n)的结点与同样深度的满二叉树中编号为i的结点在二叉树中位置完全相同,则这棵二叉树称为完全二叉树。

6.2.1 二叉树的性质
1.在二叉树的第i层上至多有2的i-1次方个结点(i>=1)。
2.深度为k的二叉树至多有2的k次方减一各结点(k>=1)。
3.对任何一棵二叉树T,如果其终端结点数叶子结点数为n0,度为2的结点树为n2,则n0=n2+1。
4.具有n个结点的完全二叉树的深度为log以2为底向下取整加一。
5.如果对一棵有n个结点的完全二叉树的结点按层编号有:1)如果i=1,则结点i时二叉树的根,无双亲;如果i>1,则其双亲是结点i/2向下取整。2)如果2i>n,则结点i无左孩子;否则其左孩子是结点2i。3)如果2i+1>n,则结点i无右孩子;佛瑞泽其右孩子是结点2i+1.

6.2.2 二叉树的存储结构

顺序存储结构:
适用于完全二叉树。

二叉链表:
一个数据域,两个孩子指针域。
定义如下:

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

6.2.3 二叉树的遍历
1.前序遍历(根左右):

void PreOrderTraverse(BiTree T){
	if(T==NULL)
		return;
	printf("%c",T->data);
	PreOrderTraverse(T->lchild);
	PreOrderTraverse(T->rchild);
}

2.中序遍历(左根右):

void InOrderTraverse(BiTree T){
	if(T==NULL)
		return;
	InOrderTraverse(T->lchild);
	printf("%c",T->data);
	InOrderTraverse(T->rchild);
}

3.后续遍历(左右根):

void PostOrderTraverse(BiTree T){
	if(T==NULL)
		return;
	PostOrderTraverse(T->lchild);
	PostOrderTraverse(T->rchild);
	printf("%c",T->data);
}

4.层次遍历:

5.二叉树遍历的性质:1)已知前序和中序遍历,可以唯一确定一棵二叉树。2)已知后序和中序遍历,唯一确定一棵二叉树。

6.2.4 二叉树的建立
对于一棵普通的二叉树,若结点无左右孩子,用“#”补全。拿到前序、中序或者后续即可复原二叉树。
其已知前序实现代码如下:

void CreateBiTree(BiTree *T){
	TElemType ch;
	scanf("%c",&ch);
	if(ch=='#')
		*T=NULL;
	else{
		*T=(BiTree)malloc(sizeof(BiTNode));
		if(!*T)
			exit(OVERFLOW);
		(*T)->data=ch;
		CreateBiTree(&(*T)->lchild);
		CreateBiTree(&(*T)->rchild);
		}	
}

6.2.5 线索二叉树
我们把这种指向前驱和后继的指针称为线索,加上线索的二叉链表称为线索链表,相应的二叉树称为线索二叉树。即结点无孩子或者孩子不全,加上前驱或者后继即可。

我们对二叉树以某种次序遍历使其变为线索二叉树的过程称为线索化。

定义:

typedef enum{Link,Thread} PointerTag;

typedef struct{
	TElemType data;
	struct BiThrNode *lchild,*rchild;
	PointerTag LTag;
	PointerTag RTag;
}BiThrNode ,*BiThrTree;

中序遍历线索化:

BiThrTree pre;

void InThreading(BiThrTree p){
	if(p){
		InThreading(p->lchild);
		if(!p->lchild){
			p->LTag=Thread;
			p->lchild=pre;
		}
		if(!pre->rchild){
			pre->RTag=Thread;
			pre->rchild=p;
		}
		pre=p;
		InThreading(p->rchild);
	}
}

中序遍历:
让中序遍历的第一个结点的左孩子和最后一个结点的右孩子都指向头结点,头结点的左孩子指向根节点,头结点的右孩子指向中序最后一个结点。

Status InOrderTraverse_Thr(BiThrTree T){
	BiThrTree p;
	p=T->lchild;
	while(p!=T){
		while(p->LTag==Link)
			p=p->lchild;
		printf("%c",p->data);
		while(p->RTag==Thread&&p->rchild!=T){
			p=p->rchild;
			printf("%c",p->data);
		}
		p=p->rchild;
	}
	return OK;
}

6.2.6 树转换

树转换为二叉树
1、加线。在所有兄弟结点之间加一条连线。
2、去线。对树中每个结点,只保留它与第一个孩子结点的连线,删除它与其他孩子结点之间的连线。
3.层次调整。以树的根结点为轴心,将整棵树顺时针旋转一定的角度,使之结构层次分明。注意第一个孩子是二叉树结点的左孩子,兄弟转换过来的孩子是结点的右孩子。

森林转换为二叉树
1.把每个树转换为二叉树。
2.第一棵二叉树不动,从第二棵二叉树开始,依次把后一棵二叉树的根结点作为前一棵二叉树的根结点的右孩子,用线连接起来当所有的二叉树连接起来后就得到了由森林转换来的二叉树。

6.2.7 哈夫曼树

从树中一个结点到另一个结点之间的分支构成两个结点之间的路径,路径上的分支数目称做路径长度

树的路径长度就是从树根到每一结点的路径长度之和。

如果考虑到带权的结点,结点的带权的路径长度为从该结点到树根之间的路径长度与结点上权的乘积。

假设有n个权值{WI,W2,•••,Wn),构造一棵有n个叶子结点的二叉树,每个叶子结点带权Wk,每个叶子的路径长度为我们通常记作,则其中带权路径长度WPL最小的二叉树称做赫夫曼树

构造霍夫曼树的哈夫曼算法:
1、根据给定的n个权值{ WI,wz, ••,wn }构成n棵二叉树的集合F={ Tl,T2,•••,Tn },其中每棵二叉树Ti中只有一个带权为根结点,其左右子树均为空。
2、在F中选取两棵根结点的权值最小的树作为左右子树构造一棵新的二叉树,且置新的二又树的根结点的权值为其左右子树上根结点的权值之和。
3、在F中删除这两棵树,同时将新得到的二叉树加入F中。
4、重复2和3步骤,直到F只含一棵树为止。这棵树便是赫夫曼树。

第七章 图

7.1 基本概念

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

无向边:若顶点到之间的边没有方向,则称这条边为无向边(Edge),用无序偶对(Vi,Vj)来表示。
有向边:若从顶点到的边有方向,则称这条边为有向边,也称为弧(Arc)。<Vi,Vj>Vi头,Vj尾。

在图中,若不存在顶点到其自身的边,且同一条边不重复出现,则称这样的图为简单图
在无向图中,如果任意两个顶点之间都存在边,则称该图为无向完全图
在有向图中,如果任意两个顶点之间都存在方向互为相反的两条弧,则称该图为有向完全图
有很少条边或弧的图称为稀疏图,反之称为稠密图

这种与图的边或弧相关的数叫做权 (Weight)。这种带权的图通常称为

对于无向图G= (V,{E}),如果边(v,v’)€ E,则称顶点v和v’互为邻接点(Adjacent),即v和v’相邻接。边( (v,v’))依附(incident)于顶点v和或者说 (v,v’)与顶点v和v’相关联。顶点v的度(Degree)是和v相关联的边的数目,记为 TD (v)。

对于有向图G= (V,{E}),如果弧<v,v’>€E,则称顶点v邻接到顶点v’,顶点v’邻接自顶点v。弧<v,v’>和顶点v,v’相关联。以顶点v为头的弧的数目称为v的人度 (InDegree),记为ID (v);以v为尾的弧的数目称为v的出度(OutDegree),记为 OD (v);顶点v的度为TD (v) =ID (v) +OD (v)。

路径的长度是路径上的边或弧的数目。
第一个顶点到最后一个顶点相同的路径称为回路或环(CYCk)。序列中顶点不重复出现的路径称为简单路径。除了第一个顶点和最后一个顶点之外,其余顶点不重复出现的回路,称为简单回路或简单环

在无向图G中,如果从顶点v到顶点v’有路径,则称v和v’是连通的。如果对于图中任意两个顶点vi、vi€V,vi和vj都是连通的,则称G是连通图(Connected Graph)

无向图中的极大连通子图称为连通分量。注意连通分量的概念,它强调:
1.要是子图;
2.子图要是连通的;
3.连通子图含有极大顶点数;
4.具有极大顶点数的连通子图包含依附于这些顶点的所有边。

有向图G中,如果对于每一对vi、vj€V、vi!=vj,从vi到vj和从vj到vi都存在路径,则称G是强连通图。有向图中的极大强连通子图称做有向图的强连通分量

所谓的一个连通图的生成树是一个极小的连通子图。它含有图中全部的n个顶点,但只有足以构成一棵树的n-1条边。

如果一个有向图恰有一个顶点的入度为0,其余顶点的入度均为1,则是一棵有向树
一个有向图的生成森林由若干棵有向树组成,含有图中全部顶点,但只有足以构成若干棵不相交的有向树的弧。

7.2 图的五种存储结构

1.邻接矩阵
图的邻接矩阵(Adjacency Matrix)存储方式是用两个数组来表示图。一个一维数组存储图中顶点信息,一个二维数组(称为邻接矩阵)存储图中的边或弧的信息。
定义:

typedef char VertexType;//定点类型
typedef int EedgType;//边权值
#define MASIZE 100;
#define infinity 65535;

typedef struct{
	VertexType vexs[MASIZE];
	EedgType arc[MASIZE][MASIZE];
	int numVertexes,numEdges;
}MGraph;

无向图的构建:

void CreateMGraph(MGraph *G){
	int i,j,k,w;
	printf("输入顶点数和边数\n");
	scanf("%d,%d",&G->numVertexes,&G->numEdges);
	for(i=0;i<G->numVertexes;i++)
		scanf(&G->vexs[i]);
	for(i=0;i<G->numVertexes;i++)
		for(j=0;j<G->numVertexes;j++)
			G->arc[i][j]=infinity;
	for(k=0;k<G->numVertexes;k++){
		printf("输入下标i,j,权值w\n");
		scanf("%d,%d,%d",&i,&j,&w);
		G->arc[i][j]=w;
		G->arc[j][i]=G->arc[i][j]
	}
}

2.邻接表
数组加链表实现,即数组存储所有顶点,单链表存储该顶点各个邻接点。
定义如下:

typedef char VertexType;
typedef int EdgeType;

//边表结点
typedef struct{
	int adjvex;//下标
	EdgeType weight;//权值
	struct EdgeNode *next;//指向下一个邻接点
}EdgeNode;

//顶点集合
typedef  struct{
	VertexType data;//顶点数据域
	EdgeType *firstedge;//边表头指针
}VertexNode,AdjList[MAXVEX];

typedef struct{
	AdjList adjList;
	int numVertexes,numEdges;//顶点数和边数
}GraphAdjList;

邻接表无向图的创建:

void CreateALGraph(GraphAdjList *G){
	int i,j,k;
	EdgeNode *e;
	printf("请输入边数和顶点数");
	scanf("%d,%d",&G->numVertexes,&G->numEdges);
	for(i=0;i<G->numVertexes;i++){//输入各个顶点信息
		scanf(&G->adjList[i].data);
		G->adjList[i].firstedge=NULL;
	}
	for(k=0;k<G->numEdges;k++){
		printf("输入边序号");
		scanf("%d,%d",&i,&j);
		e = (*EdgeNode)malloc(sizeof(EdgeNode));
		e->adjvex=j;
		e->next=G->adjList[i].firstedge;
		G->adjList[i].firstedge=e;
		
		e = (*EdgeNode)malloc(sizeof(EdgeNode));
		e->adjvex=i;
		e->next=G->adjList[j].firstedge;
		G->adjList[j].firstedge=e;
		
	}
}

3.十字链表
将邻接表和逆邻接表结合,这样有向图的出度和入度都可以解决。

4.邻接多重表

5.边集数组

7.3 图的遍历

1.深度优先遍历DFS
邻接矩阵遍历:

typedef int Boolean;
Boolean visited[MAX];

void DFS(MGraph G,int i){
	int j;
	visited[i]=TRUE;
	printf("%d",G->vexs[i]);
	for(j=0;j<G->numVertexes;j++){
		if(G->arc[i][j]==1&&!visited[j]){
			DFS(G,j);//邻接点递归调用
		}
	}
}

void DFSTraverse(MGraph G){
	int i;
	for(i=0;i<G.numVertexes;i++){
		visited[i]=FALSE;
	}
	for(i=0;i<G.numVertexes;i++){
		if(!visited[i])//对于连通图只会执行一次,非连通图会执行多次。
			DFS(G,i);
	}
}

邻接表遍历:

typedef int Boolean;
Boolean visited[MAX];

void DFS(GraphAdjList GL,int i){
	EdgeNode *p;
	visited[i]=TRUE;
	printf("%d",GL->adjList[i].data);
	p=GL->adjList[i].firstedge;
	while(p){
		if(!visited[p->adjvex])
			DFS(GL,p->adjvex);
		p=p->next;
	}
}

void DFSTraverse(GraphAdjList GL){
	int i;
	for(i=0;i<GL->numVertexes;i++){
		visited[i]=FALSE;
	}
	for(i=0;i<G->numVertexes;i++){
		if(!visited[i])//对于连通图只会执行一次,非连通图会执行多次。
			DFS(GL,i);
	}
}

2.广度优先算法BFS
邻接矩阵的广度优先遍历:

void BFSTraverse(MGraph G){
	int i,j;
	Queue Q;
	for(i=0;i<G.numVertexes;i++)
		visited[i]=FALSE;
	InitQueue(&Q);
	for(i=0;i<G.numVertexes;i++){
		if(!visited[i]){
			visited[i]=TRUE;
			printf("%c",G.vexs[i]);
			EnQueue(&Q,i);
			while(!QueueEmpty(Q)){
				DeQueue(&Q,&i);
				for(j=0;j<G.numVertexes;j++){
					if(G.arc[i][j]==1&&!visited[j]){
						visited[j]=TRUE;
						printf("%c",G.vexs[j]);
						EnQueue(&Q,j);
					}
				}
			}
		}
	}
}

邻接表的广度优先遍历:

void BFSTraverse(GraphAdjList GL){
	int i;
	EdgeNode *p;
	Queue Q;
	for(i=0;i<GL->numVertexes;i++){
		visited[i]=FALSE;
	InitQueue(&Q);
		for(i=0;i<GL->numVertexes;i++){
			if(!visited[i]){
				visited[i]=TRUE;
				printf("%c",GL->adjList[i].data);
				EnQueue(&Q,i);
				while(!QueueEmpty(Q)){
					DeQueue(&Q,&i);
					p=GL->adjList[i].firstedge;
					while(p){
						if(!visited[p->adjvex]){
							visited[p->adjvex]=TRUE;
							printf("%c",GL->adjList[p->adjvex].data);
							EnQueue(&Q,p->adjvex);
						}
						p=p->next;
					}
				}
			}
		}
	}
}

7.4 最小生成树
我们把构造连通网的最小代价生成树称为最小生成树。

1.普利姆算法
假设N=(P,{E})是连通网,TE是N上最小生成树中边的集合。算法从U={u₀} (u₀∈V), TE={}开始。重复执行下述操作:在所有u∈U,v∈V-U的边(u,v) ∈E中找一条代价最小的边(u₀,v₀)并入集合TE,同时v₀并人U,直至U=V为止。此时TE 中必有n一1条边,则T= (V,{TE))为N的最小生成树。
具体实现代码:

void MinSpanTree_Prim(MGraph G){
	int min,i,j,k;
	int adjvex[MAXVEX];//存放相关顶点下标
	int lowcost[MAXVEX];//存放相关顶点间边的权值
	lowcost[0]=0;//初始化第一个权值,即v₀加入生成树
	adjvex[0]=0;//初始化第一个顶点下标为0
	for(i=1;i<G.numVertexes;i++){
		lowcost[i]=G.arc[0][i];
		adjvex[i]=0;
	}
	for(i=1;i<G.numVertexes;i++){
		min=INFINITY;
		j=1;k=0;
		while(j<G.numVertexes){
			if(lowcost[j]!=0&&lowcost[j]<min){
				min=lowcost[j];
				k=j;
			}
			j++;
		}
		printf("%d,%d",adjvex[k],k);
		lowcost[k]=0;//此顶点已完成任务
		for(j=1;j<G.numVertexes;j++){
			if(lowcost[j]!=0&&G.arc[k][j]<lowcost[j]){
				lowcost[j]=G.arc[k][j];
				adjvex[j]=k;
			}
		}
	}
}

2.克鲁斯卡尔算法
假设N= (V,{E})是连通网,则令最小生成树的初始状态为只有n个顶点而无边的非连通图T={V,{}},图中每个顶点自成一个连通分量。在E中选择代价最小的边,若该边依附的顶点落在T中不同的连通分量上,则将此边加入到T中,否则舍去此边而选择下一条代价最小的边。依次类推,直至T中所有顶点都在同一连通分量上为止。

采用边集数组实现:

typedef struct{
	int begin;
	int end;
	int weight;
}Edge;

//其中省略了邻接矩阵转换为边集数组并从小到大排序过程
void MinSpanTree_Kruskal(MGraph G){
	int i,n,m;
	Edge edges[MAXEDGE];
	int parent[MAXVEX];//定义一维数组用来判断边与边是否形成环路
	for(i=0;i<G.numVertexes;i++)
		parent[i]=0;//初始化为0
	for(i=0;i<G.numEdges;i++){
		n=Find(parent,edges[i].begin);
		m=FInd(parent,edges[i].end);
		if(n!=m){
			parent[n]=m;
			printf("%d,%d,%d",edges[i].begin,edges[i].end,edges[i].weight);
		}
	}
}

int Find(int *parent,int f){//查找连线顶点的尾部下标
	while(parent[f]>0)
		f=parent[f];
	retrun f;
}

7.5 最短路径

1.迪杰斯特拉算法

#define MAXVEX 9
#define INFINITY 65535
typedef int Pathmatirx[MAXVEX];//用于存储最短路径下标的数组
typedef int ShortPathTable[MAXVEX];//用于存储到各殿最短路径的权值和

//求无向图G的v0顶点到其余顶点v最短路径P[v]及带权长度D[v]
void ShortestPath_dijkstra(MGraph G,int v0,Pathmatirx *P,ShortPathTable *D){
	int v,w,k,min;
	int final[MAXVEX];//表示以求得顶点v0到vw的最短路径
	for(v=0;v<G.numVertexes;v++){//初始化数据
		final[v]=0;//全部为求得
		(*D)[v]=G.matirx[v0][v];//将v0点有连线的顶点加上权值
		(*P)[v]=0;//初始化路径数组P为0
	}
	(*D)[v0]=0;//v0到v0路径为0
	final[v0]=1;//v0到v0不需要求路径
	//开始主循环,每次求得v0到某一个v顶点的最短路径
	for(v=1;v<G.numVertexes;v++){
		min=INFINITY;
		for(w=0;w<G.numVertexes;w++){
			if(!final[w]&&(*D)[w]<min){
				k=w;
				min=(*D)[w];
			}
		}
		final[k]=1;
		for(w=0;w<G.numVertexes;w++){
			if(!fianl[w]&&(min+G.matirx[k][w]<(*D)[w])){
				(*D)[w]=min+G.matirx[k][w];
				(*P)[w]=k;
			}
		}
	}
}

2.弗洛伊德算法

typedef int Pathmatirx[MAXVEX][MAXVEX];
typedef int ShortPathTable[MAXVEX][MAXVEX];

void ShortestPath_Floyd(MGraph G,Pathmatirx *P,ShortPathTable *D){
	int v,w,k;
	for(v=0;v<G.numVertexes;++v){
		for(w=0;w<G.numVertexes;++w){
			(*D)[v][w]=G.matirx[v][w];
			(*P)[v][w]=w;
		}
	}
	for(k=0;k<G.numVertexes,++k){
		for(v=0;v<G.numVertexes;++v){
			for(w=0;w<G.numVertexes;++w){
				if((*D)[v][w]>(*D)[v][k]+(*D)[k][w]){
					(*D)[v][w]=(*D)[v][k]+(*D)[k][w];
					(*P)[v][w]=(*P)[v][k];
				}
			}
		}
	}

	//求最短路径显示代码
	for(v=0;v<G.numVertexes;++v){
		for(w=v+1;w<G.numVertexes;w++){
			printf("v%d-v%d weight:%d",v,w,D[v][w]);
			k=P[v][w];
			printf("path:%d",v);
			while(k!=w){
				printf("->%d",v);
				k=P[k][w];
			}
			printf("->%d",w);
		}
		printf("\n");
	}
}

7.6 拓扑排序
拓扑排序,其实就是对一个有向图构造拓扑序列的过程。
7.7 关键路径
我们把各个活动所持续的时间之和为路径长度,从源点到汇点具有最大的长度的路径叫关键路径,在关键路径上的活动叫做关键活动。

第八章 查找

8.1 折半查找

int Binary_Search(int *a,int n,int key){
	int low,high,mid;
	low=1;
	high=n;
	while(low<=high){
		mid=(low+high)/2;
		if(a[mid]<key){
			low=mid+1;
		}else if(a[mid]>key){
			high=mid-1;
		}else{
			return mid;
		}
	}
	return 0;
}

时间复杂度:O(logn)
最好:O(1)
最坏:O(logn)

8.2 插值查找

是折半查找的改进在这里插入图片描述
适用于表长且均匀的数据。

8.3 斐波那契查找

利用斐波那契进行分割,左边的比右边大。

//需要定义一个斐波那契数组F[n]
int Fibonacci_Search(int *a,int n,int key){
	int low,high,mid,i,k;
	low=1;
	high=n;
	k=0;
	while(n>F[k]-1)//找到数组最大下标在斐波那契数组中的位置
		k++;
	for(i=n;i<F[k]-1;i++)//不够的补齐
		a[i]=a[n];
	while(low<=high){
		mid=low+F[k-1]-1;
		if(key<a[mid]){
			high=mid-1;
			k=k-1;
		}else if(key>a[mid]){
			low=mid+1;
			k=k-2;
		}else{
			if(mid<=n)
				return mid;
			else
				return n;
		}
	}
	return 0;
}

8.4 二叉排序树
二叉排序树,又称为二叉查找树。它是一颗空树,或者是具有以下性质的二叉树。

若它的左子树不空,则左子树上的节点的值均小于他的根节点的值。
若它的右子树不空,则右子树上的节点的值均大于他的根节点的值。
它的左右子树分别为二叉排序树。

二叉树的结构

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

二叉排序树的查找过程:

/*f是双亲结点,递归时有用,开始是为NULL,p用于保存查找到结点元素,或者未找到保存双亲结点,插入时可以很方便的插入*/
Status SearchBST(BiTree T,int key,BiTree f,BiTree *p){
	if(!T){//未查找到
		*p=f;
		return FALSE;
	}else if(key==T->data){//查找到
		*p=T;
		return TRUE;
	}else if(key<T->data){
		return SearchBST(T->lchild,key,T,p);
	}else{
		return SearchBST(T->rchild,key,T,p);
	}
}

二叉查找树的插入:

Status InsertBST(BiTree *T,int key){
	BiTree p,s;
	if(!SearchBST(*T,key,NULL,&p)){
		s=(BiTree)malloc(sizeof(BiTNode));
		s->data=key;
		s->lchild=s->rchild=NULL;
		if(!p){
			*T=s;//树为空,直接为根结点
		}else if(key<p->data){
			p->lchild=s;
		}else{
			p->rchild=s;
		}
		return TRUE;
	}else{
		return FALSE;
	}
}

二叉排序树的创建:

int i;
int a[10]={1,56,78,36,41,78,11,6565,1215,7878};
BiTree T=NULL:
for(i=0;i<10;i++){
	InsertBST(&T,a[i]);
}

二叉排序树的删除:
三种情况:
叶子结点;
仅有左子树或右子树的结点;
左右子树都有的结点。

Status DeleteBST(BiTree *T,int key){
	if(!*T)
		return FALSE;//不存在
	else{
		if(key==(*T)->data)
			return Delete(T);
		else if(key<(*T)->data)
			return DeleteBST(&(*T)->lchild,key);
		else
			return DeleteBST(&(*T)->rchild,key);
	}
} 

Status Delete(BiTree *p){
	BiTree q,s; 
	if((*p)->lchild==NULL){
		q=*p;(*p)=(*p)->rchild;free(q);
	}else if((*p)->rchild==NULL){
		q=*p;(*p)=(*p)->lchild;free(q);
	}else{
		q=*p;s=(*p)->lchild;
		while(s->rchild){
			q=s;s=s->rchild;
		}
		(*p)->data=s->data;
		if(q!=(*p)){
			q->rchild=s->lchild;
		}else{
			q->lchild=s->lchild;
		}
		free(s);
	}
	return TRUE;
}

8.5 平衡二叉树(AVL树)
平衡二叉树,是一种二叉排序树,其中每一个结点的左子树和右子树的高度差至多等于1。
我们将二叉树上结点的左子树深度减去右子树深度的值称为平衡因子BF
距离插入结点最近的,且平衡因子的绝对值大于1的结点为根的子树,我们称为最小不平衡树

AVL树实现
定义:

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

右旋:

void R_Rotate(BiTree *P){
	BiTree L;
	L=(*P)->lchild;
	(*P)->lchild=L->rchild;
	L->rchild=(*P);
	*P=L;
}

左旋:

void L_Rotate(BiTree *P){
	BiTree R;
	R=(*P)->rchild;
	(*P)->rchild=L->lchild;
	R->lchild=(*P);
	*P=R;
}

左平衡:

#define LH +1
#define EH +1
#define RH +1
void LeftBalance(BiTree *T){
	BiTree L;Lr;
	L=(*T)->lchild;
	switch(L->bf){
		case LH:
			(*T)->bf=L->bf=EH;
			R_Rotate(T);
			break;
		case RH:
			Lr=L->rchild;
			switch(Lr->bf){
				case LH:(*T)->bf=RH;
					L->bf=EH;
					break;
				case EH:(*T)->bf=L->bf=EH;
					break;
				case RH:(*T)->bf=EH
					L->bf=LH;
					break;
			}
			Lr->bf=EH;
			L_Rotate(&(*T)->lchild);
			R_Rotate(T);
			break;
	}
}

插入:

Status InsertAVL(BiTree *T,int e,Status *taller){
	if(!*T){
		*T=(BiTree)malloc(sizeof(BiTreeNode));
		(*T)->data=e;
		(*T)->lchild=(*T)->rchild=NULL:
		(*T)->bf=EH;
		*taller=TRUE;
	}else{
		if(e==(*T)->data){
			*taller=FALSE;
			return FALSE;
		}
		if(e<(*T)->data){
			if(!InsertAVL(&(*T)->lchild,e,taller)
				return FALSE;
			if(taller){
				switch((*T)->bf){
					case LH:
						LeftBalance(T);
						*taller=FALSE;
						break;
					case EH:
						(*T)->bf=LH;
						*taller=TRUE;
						break;
					case RH:
						(*T)->bf=EH;
						*taller=FALSE;
						break;
				}
			}
		}else{
			if(!InsertAVL(&(*T)->lchild,e,taller)
				return FALSE;
			if(taller){
				switch((*T)->bf){
					case LH:
						(*T)->bf=EH;
						*taller=FALSE;
						break;
					case EH:
						(*T)->bf=RH;
						*taller=TRUE;
						break;
					case RH:
						RightBalance(T);
						*taller=FALSE;
						break;
				}
			}
		}
	}
	return TRUE;
}

生成一棵AVL树:

int i;
int a[10]={3,2,45,1,45,1,545,133,1212,23};
BiTree T=NULL;
Status taller;
for(i=0;i<10;i++){
	InsertAVL(&T,a[i],taller);
}

8.6 B树
多路查找树,其中每一个结点的孩子树可以多余两个,且每一个结点处可以存储多个元素。

2-3树
其中每一个结点都具有两个或者三个孩子。

2-3-4树
其中每一个结点都具有两个或者三个或者四个孩子。

B树
B树是一种平衡的多路查找树,2-3树和2-3-4都是B树的特列。结点最大的孩子数目称为B树的阶。

8.7 B+树
B树的变种,出现在分支结点中的元素会被当做它们在该分支节点位置的中序后继者(叶子节点)中再次列出。另外,每一个叶子节点购汇保存一个指向后一叶子结点的指针。

8.8 哈希表
散列技术是在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使得每一个关键字key对应的一个存储位置f(key)。
我们称这种对应关系f为散列函数,又称为哈希函数。采用散列技术将记录村塾在一块连续的存储空间中,我们将连续存储空间称为散列表。

散列函数的构造方法
1.直接定址法
f(key)=axkey+b(a,b为常数)
2.数字分析法
数字较大,抽取中间一部分,若出现冲突再进行处理。
3.平方取中法
求平方后取中间的数字作为散列地址。
4.折叠法
将关键字分割几部分,再叠加求和,按散列表长度,取后几位作为散列地址。
5.除留余数法
f(key)=key mod p (p<=m)
6.随机数法
选择一个随机数,取关键字的随机函数值为它的散列地址。
f(key)=random(key)

处理散列冲突的方法
1.开放地址法
一旦发生了冲突,就去寻找下一个空的散列地址,只要散列地址足够大,总能找到。
f1(key)=(f(key)+d1)MOD m (d1=1,2,3,…,m-1)
2.再散列函数法
一旦发生了冲突,就换一种散列函数计算。
f1(key)=RHi(key) (i=1,2,…,k)
3.链地址法
发生冲突,在其存储位置给单链表增加结点。
4.公共溢出法
把发生冲突的关键字建立一个公共的溢出区来存放。

散列表查找实现
定义:

#define SUCCESS 1
#define UNSUCCESS 0
#define HASHSIZE 12 //定义散列表长度
#define NULLKEY -32768
typedef struct{
	int *elem;//存储元素,动态分配
	int count;//当前元素个数
}HashTable;
int m=0;//散列表长,全局变量

初始化:

Status InitHashTable(HashTable *H){
	int i;
	m=HASHSIZE;
	H->count=m;
	H->elem=(int *)malloc(m*sizeof(int));
	for(i=0;i<m;i++)
		H->elem[i]=NULLKEY;
	return OK;
}

hash函数:

int Hash(int key){
	return key%m;//除留余数法
}

插入关键字:

void InsertHash(HashTable *H,int key){
	int addr=Hash(key);
	while(H->elem[addr]!=NULLKEY)
		addr=(addr+1)%m;//开放地址法线性探索
	H->elem[addr]=key;
}

散列表查找:

Status SearchHash(HashTable H,int key,int *addr){
	*addr=Hash(key);
	while(H.elem[*addr]!=key){
		*addr=(*addr+1)%m;
		if(H.elem[*addr]==NULLKEY||*addr==Hash(key))[
			return UNSUCCESS;
		]
	}
	return SUCCESS;
}

第九章 排序

9.1 冒泡排序

void BubbleSort(SqList *L){
	int i,j;
	for(i=1;i<L->length;i++){
		for(j=L->length-1;j>=i;j--){
			if(L->r[i]>L->r[j+1])
				swap(L,j,j+1);
		}
	}
}

时间复杂度:
O(n2)
优化后最好:O(n)

优化的冒泡排序:

void BubbleSort(SqList *L){
	int i,j;
	Status flag=TRUE;
	for(i=1;i<L->length&&flag;i++){
		flag=FALSE;
		for(j=L->length-1;j>=i;j--){
			if(L->r[i]>L->r[j+1])
				swap(L,j,j+1);
				flag=TRUE;
		}
	}
}

9.2 简单选择排序

void SelectSort(SqList *L){
	int i,j,min;
	for(i=1;i<L->length;i++){
		min=i;
		for(j=i+1;j<=L->length;j++){
			if(L->r[min]>L->r[j])
				min=j;
		}
		if(i!=min)
			swqp(L,i,min);
	}
}

9.3 直接插入排序

void InsertSort(SqList *L){
	int i,j;
	for(i=2;i<=L->length;i++){
		if(L->r[i]<L->r[i-1]){
			L->r[0]=L->r[i];
			for(j=i-1;L->r[j]>L->r[0];j--)
				L->r[j+1]=L->r[j];
			L->r[j+1]=L->r[0];
		}
	]
}

时间复杂度:
O(n2)
最好:
O(n)

9.4 希尔排序

void ShellSort(SqList *L){
	int i,j;
	int increment=L->length;
	do{
		increment=increment/3+1;
		for(i=increment+1;i<=L->length;i++){
			L->r[0]=L->r[i];
			for(j=i-increment;j>0&&L->r[0]<L->r[j];j-=increment)
				L->r[j+increment]=L->r[j];
			L->r[j+increment]=L->r[0];
		}
	}while(increment>1)
}

时间复杂度:
O(n3/2)

9.5 堆排序
9.6 归并排序
9.7 快速排序

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值