0
点赞
收藏
分享

微信扫一扫

考研笔记——王道C语言

落拓尘嚣 2022-03-19 阅读 105
c语言

写在前面的话

基于王道龙哥的代码写的笔记,为了加深印象,同时也为了后期复习。

初级阶段太简单了,中级阶段其实也简单,但以前没有写结构体的习惯,习惯直接把结构体拆成数组写,现在觉得还是挺方便的,主要也是为了这个才做的笔记,把一堆数据结构放在一起便于对比。高级阶段的什么文件、OS那块还没看,如果后期写了笔记还会补。

主要是面向我自己写的,不为教学也不为扩列,所以别指指点点,有哪里写的不对可以说,别在那balabala什么写的太浅,就是复制代码什么的。我完全为了自己,只是觉得发博客就是顺手的事。

主要就是将龙哥的代码模块化(毕竟代码长的话读起来也没有逻辑性,从main开始读还得跳来跳去),更改了一下代码顺序(按照逻辑思维顺序),以及分了一些层次,加了一些注释和解释。再然后就是把很多数据结构放一起好对比。

如果后期做了其他笔记也会发出来,欢迎交流

线性表

定义:n个相同类型元素组成的有序集合

1. 个数有限
2. 数据类型相同
3. 逻辑上有序

线性表的顺序表示

静态分配的数组(SqList)

数据结构

typedef struct{
	ElemType data[MaxSize];
	int length;//当前顺序表中有多少个元素
}SqList;

插入元素

​ 传入:结构体变量,插入位置,插入元素

​ 返回:ture or false

//i代表插入的位置(即序数=下标+1),从1开始,e要插入的元素(&为引用)
bool ListInsert(SqList &L,int i,ElemType e)
{
	if(i<1||i>L.length+1)//判断要插入的位置是否合法
		return false;
	if(L.length>=MaxSize)//超出空间了
		return false;
	for(int j=L.length;j>=i;j--)//移动顺序表中的元素
		L.data[j]=L.data[j-1];
	L.data[i-1]=e;//数组下标从零开始,插入第一个位置,访问的下标为0
	L.length++;
	return true;
}

删除元素

​ 传入:结构体变量,删除位置,元素类型变量(通过引用的方式得到输出值)

​ 返回:ture or false

//删除使用元素e的引用的目的是拿出对应的值
bool ListDelete(SqList &L,int i,ElemType &e)
{
	if(i<1||i>L.length)//如果删除的位置是不合法
		return false;
	e=L.data[i-1];//获取顺序表中对应的元素,赋值给e
	for(int j=i;j<L.length;j++)
		L.data[j-1]=L.data[j];
	L.length--;//删除一个元素,顺序表长度减1
	return true;
}

动态分配的数组(SeqList)

数据结构

typedef struct{
	ElemType *data;//数组的首地址(地址指针)
	int capacity;//动态数组的最大容量
	int length;//当前已存数量
}SeqList;

线性表的链式表示

有头结点的单链表(LNode,*LinkList)

数据结构

typedef struct LNode{
	ElemType data;
	struct LNode *next;//指向下一个结点 
}LNode,*LinkList;

头插法新建链表

​ 传入:结构体变量(未初始化,所以需要引用)

​ 返回:结构体变量(可有可无)

LinkList CreatList1(LinkList &L)//list_head_insert
{
	LNode *s;
    int x;
	L=(LinkList)malloc(sizeof(LNode));//带头结点的链表
	L->next=NULL;//L->data里边没放东西
	scanf("%d",&x);//从标准输入读取数据
	//3 4 5 6 7 9999
	while(x!=9999){
		s=(LNode*)malloc(sizeof(LNode));//申请一个新空间给s,强制类型转换
		s->data=x;//把读取到的值,给新空间中的data成员
		s->next=L->next;//让新结点的next指针指向链表的第一个元素(第一个放我们数据的元素)
		L->next=s;//让s作为第一个元素
		scanf("%d",&x);//读取标准输入
	}
	return L;
}

尾插法新建链表

​ 传入:结构体变量(未初始化,所以需要引用)

​ 返回:结构体变量(可有可无)

LinkList CreatList2(LinkList &L)//list_tail_insert
{
	int x;
	L=(LinkList)malloc(sizeof(LNode));//带头节点的链表
	LNode* s, * r = L;//LinkList s,r=L;也可以,r代表链表表尾结点,指向链表尾部
	//3 4 5 6 7 9999
	scanf("%d",&x);
	while(x!=9999){
		s=(LNode*)malloc(sizeof(LNode));
		s->data=x;
		r->next=s;//让尾部结点指向新结点
		r=s;//r指向新的表尾结点
		scanf("%d",&x);
	}
	r->next=NULL;//尾结点的next指针赋值为NULL
	return L;
}

双链表(DNode,*DLinkList)

数据结构

typedef struct DNode{
	ElemType data;
	struct DNode *prior,*next;//前驱,后继
}DNode,*DLinkList;

双向链表头插法(Dlist_head_insert)

DLinkList Dlist_head_insert(DLinkList &DL)
{
	DNode *s;int x;
	DL=(DLinkList)malloc(sizeof(DNode));//带头结点的链表,DL就是头结点
	DL->next=NULL;//前驱指针和后继指针都填写为NULL
	DL->prior=NULL;
	scanf("%d",&x);
	//3 4 5 6 7 9999
	while(x!=9999){
		s=(DLinkList)malloc(sizeof(DNode));
		s->data=x;
		s->next=DL->next;//新节点的后继 是 头结点的后继				  (1)离DL最远的指针(向外的)
		if(DL->next!=NULL)//第一个节点不需要(第一个节点时头结点没有后继)	
		{
			DL->next->prior=s;//头结点的后继的前驱 是 新节点				(2)离DL最远的往回的指针
		}
		s->prior=DL;//新节点的前驱 是 头结点							(3)挨着DL的指针
		DL->next=s;//头结点的后继 是 新节点							(4)挨着DL的指针
		scanf("%d",&x);//读取标准输入
	}
	return DL;
}

双向链表尾插法(Dlist_tail_insert)

DLinkList Dlist_tail_insert(DLinkList &DL)
{
	int x;
	DL=(DLinkList)malloc(sizeof(DNode));//带头节点的链表
	DNode *s,*r=DL;//r代表尾指针
	DL->prior=NULL;//初始化前驱,DL的前驱为空,后继会被赋值所以无所谓
	//3 4 5 6 7 9999
	scanf("%d",&x);
	while(x!=9999){
		s=(DNode*)malloc(sizeof(DNode));
		s->data=x;
		r->next=s;				//连接原尾巴和新节点
		s->prior=r;				//连接原尾巴和新节点
		r=s;//r指向新的表尾结点
		scanf("%d",&x);
	}
	r->next=NULL;//尾结点的next指针赋值为NULL
	return DL;
}

删除节点

bool DListDelete(DLinkList DL,int i)
{
	DLinkList p=GetElem(DL,i-1);
	if(NULL==p)
	{
		return false;
	}
	DLinkList q;
	q=p->next;
	if(q==NULL)
		return false;
	p->next=q->next;//断链的操作
	if(q->next!=NULL)//q->next为NULL删除的是最后一个结点
	{
		q->next->prior=p;//断链的操作(只有删除的节点不是尾节点才执行)
	}
	free(q);
	return true;
}

循环双链表

静态链表

线性栈(SqStack)

数据结构

typedef struct {
	ElemType data[MaxSize];//数组
	int top;
}SqStack;
void InitStack(SqStack& S)
{
	S.top = -1;//代表栈为空
}

出入栈

//入栈
bool Push(SqStack& S, ElemType x)
{
	if (S.top == MaxSize - 1)//数组的大小不能改变,避免访问越界
	{
		return false;
	}
	S.data[++S.top] = x;
	return true;
}
//出栈
bool Pop(SqStack& S, ElemType& x)
{
	if (-1 == S.top)
		return false;
	x = S.data[S.top--];//后减减,x=S.data[S.top];S.top=S.top-1;
	return true;
}

#数组实现的循环队列(SqQueue)

数据结构

typedef struct{
	ElemType data[MaxSize];//数组,存储MaxSize-1个元素
	int front,rear;//队列头 队列尾(队列头指向队列的第一个元素,队列尾指向可以装入的第一个位置)
}SqQueue;

void InitQueue(SqQueue &Q)
{
	Q.rear=Q.front=0;
}

入队(EnQueue)

bool EnQueue(SqQueue &Q,ElemType x)
{
	if((Q.rear+1)%MaxSize==Q.front) //判断是否队满
		return false;
	Q.data[Q.rear]=x;//3 4 5 6
	Q.rear=(Q.rear+1)%MaxSize;
	return true;
}

出队(DeQueue)

bool DeQueue(SqQueue &Q,ElemType &x)
{
	if(Q.rear==Q.front)
		return false;
	x=Q.data[Q.front];//先进先出
	Q.front=(Q.front+1)%MaxSize;
	return true;
}

#链表实现的循环队列(LinkQueue嵌套LinkNode)

数据结构

typedef struct LinkNode{
	ElemType data;
	struct 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 IsEmpty(LinkQueue Q)
{
	if(Q.front==Q.rear)
		return true;
	else
		return false;
}

入队

//入队,尾部插入法
void EnQueue(LinkQueue &Q,ElemType x)
{
	LinkNode *s=(LinkNode *)malloc(sizeof(LinkNode));
	s->data=x;
	s->next=NULL;
	Q.rear->next=s;//rear始终指向尾部
	Q.rear=s;
}

出队

bool DeQueue(LinkQueue &Q,ElemType &x)
{
	if(Q.front==Q.rear) return false;//队列为空
	LinkNode *p=Q.front->next;//头结点什么都没存,所以头结点的下一个节点才有数据
	x=p->data;
	Q.front->next=p->next;//断链
	if(Q.rear==p)//删除的是最后一个元素(即判断要不要更新rear)
		Q.rear=Q.front;//队列置为空
	free(p);
	return true;
}

树的链式存储

二叉树建立和遍历

数据结构

一个数据结构(BiTNode,*BiTree)存树节点

一个数据结构(tag,*ptag_t)指向应当生孩子的叶子结点(用于建立完全二叉树)

typedef struct BiTNode{
	BiElemType c;//c就是书籍上的data
	struct BiTNode *lchild;
	struct BiTNode *rchild;
}BiTNode,*BiTree;

typedef struct tag{
	BiTree p;//树的某一个结点的地址值
	struct tag *pnext;
}tag_t,*ptag_t;

建立二叉树

		pnew=(BiTree)calloc(1,sizeof(BiTNode));//calloc申请空间并对空间进行初始化,赋值为0
		pnew->c=c;//数据放进去
		listpnew=(ptag_t)calloc(1,sizeof(tag_t));//给队列结点申请空间
		listpnew->p=pnew;//有多少个树节点,list就有多长,一指一
		if(NULL==tree)//建立第一个节点(根节点)
		{
			tree=pnew;//树的根
			phead=listpnew;//队列头
			ptail=listpnew;//队列尾
			pcur=listpnew;//指向当前应当生孩子的节点
			continue;
		}
		else{
			ptail->pnext=listpnew;//新结点放入链表,通过尾插法
			ptail=listpnew;//ptail指向队列尾部
		}//pcur始终指向要插入的结点的位置
		if(NULL==pcur->p->lchild)//如果父节点的左子节点为空
		{
			pcur->p->lchild=pnew;//把新结点放到要插入结点的左边
		}else if(NULL==pcur->p->rchild)//如果父节点的右子节点为空
		{
			pcur->p->rchild=pnew;//把新结点放到要插入结点的右边
			pcur=pcur->pnext;//左右都放了结点后,pcur指向队列的下一个
		}

遍历二叉树

//abdhiejcfg  前序遍历,前序遍历就是深度优先遍历
void preOrder(BiTree p)
{
	if(p!=NULL)
	{
		putchar(p->c);//等价于visit函数
		preOrder(p->lchild);
		preOrder(p->rchild);
	}
}
//中序遍历  hdibjeafcg
void InOrder(BiTree p)
{
	if(p!=NULL)
	{
		InOrder(p->lchild);
		putchar(p->c);
		InOrder(p->rchild);
	}
}
//hidjebfgca  后序遍历
void PostOrder(BiTree p)
{
	if(p!=NULL)
	{
		PostOrder(p->lchild);
		PostOrder(p->rchild);
		putchar(p->c);
	}
}
//中序遍历非递归,非递归执行效率更高,考的概率很低
void InOrder2(BiTree T)
{
	SqStack S;
	InitStack(S);
    BiTree p=T;
	while(p||!StackEmpty(S))//逻辑或||
	{
		if(p){//当一个结点不为空,压栈,并取左孩子
			Push(S,p);
			p=p->lchild;
		}
         else{//弹出栈中元素并打印,获取打印元素的右结点
			Pop(S,p);
             putchar(p->c);
			p=p->rchild;
		}
	}
}
//层次遍历,层序遍历,广度优先遍历
void LevelOrder(BiTree T)
{
	LinkQueue Q;//辅助队列
	InitQueue(Q);//初始化队列
	BiTree p;
	EnQueue(Q,T);//树根入队
	while(!IsEmpty(Q))
	{
		DeQueue(Q,p);//出队当前结点并打印
		putchar(p->c);
		if(p->lchild!=NULL) //入队左孩子
			EnQueue(Q,p->lchild);
		if(p->rchild!=NULL)  //入队右孩子
			EnQueue(Q,p->rchild);
	}
}

线索二叉树

不让子节点的指针空着,没有左孩子的时候左指针指向前驱,没有右孩子的时候右指针指向后继。

数据结构(ThreadNode)

typedef struct ThreadNode{
ElemType data;
struct ThreadNode *lchild,*rchild;
int ltag,rtag;
//ltag表示lchild是否指向前驱(or表示左孩子)
//rtag表示rchild是否指向后继(or表示右孩子)
}ThreadNode,*ThreadTree;

构建线索二叉树(在已经构建好普通二叉树的基础上更新ltag、rtag、lchild、rchild)

中序遍历过程中,p为当前指针,pre为刚刚访问的指针(即p上一时刻的值)。

当p的左指针为空则指向pre,当pre的右指针为空就指向p。

void InThread(ThreadTree &p,ThreadTree &pre)
{
if(p!=NULL){//若p为空直接返回
//中序遍历,先遍历左节点,再判断p,再遍历右节点
InThread(p->lchild,pre);//递归找树的左孩子
if(p->lchild==NULL){//左边为NULL,填写当前结点的前驱
p->lchild=pre;
p->ltag=1;
}
if(pre!=NULL&&pre->rchild==NULL){
//pre节点右孩子为NULL,就让其指向后继节点,而后继结点刚好就是p
pre->rchild=p;
pre->rtag=1;
}
pre=p;
InThread(p->rchild,pre);
}
}
void CreateInThread(ThreadTree T)
{
ThreadTree pre=NULL;//使用辅助指针pre
if(T!=NULL){
InThread(T,pre);
//遍历完之后最后一个pre的后继节点为NULL
pre->rchild=NULL;
pre->rtag=1;
}
}

二叉排序树

数据结构(BSTNode,*BiTree)

typedef struct BSTNode{
KeyType key;
struct BSTNode *lchild,*rchild;
}BSTNode,*BiTree;

插入节点(BST_Insert)

int BST_Insert(BiTree &T,KeyType k)//&T一方面可以直接更改根节点的地址(而不需要在函数外面申请),另一方面可以与return联系起来直接构建父子关系
{
if(NULL==T)
{ //为新节点申请空间,第一个结点作为树根
T=(BiTree)malloc(sizeof(BSTNode));
T->key=k;
T->lchild=T->rchild=NULL;
return 1;//代表插入成功
}
else if(k==T->key)
return 0;//发现相同元素,就不插入
else if(k<T->key)//如果要插入的结点,小于当前结点
return BST_Insert(T->lchild,k);//函数调用结束后,左孩子和原来的父亲会关联起来
else
return BST_Insert(T->rchild,k);
}

搜索节点(BST_Search)

返回搜索到的指针(p无用)

BSTNode *BST_Search(BiTree T,KeyType key,BiTree &p)
{
p=NULL;//存储要找的结点的父亲
while(T!=NULL&&key!=T->key)
{
p=T;
if(key<T->key) T=T->lchild;//比当前节点小,就左边找
else T=T->rchild;//比当前节点大,右边去
}
return T;
}

删除节点(DeleteNode)

void DeleteNode(BiTree &root,KeyType x){
if(root == NULL){
return;
}
if(root->key>x){//在左子节点下找
DeleteNode(root->lchild,x);
}else if(root->key<x){//在右子节点下找
DeleteNode(root->rchild,x);
}
else{ //查找到了删除节点
if(root->lchild == NULL){ //左子树为空,右子树直接顶上去
BiTree tempNode = root;//用临时的存着的目的是一会要free
root = root->rchild;
free(tempNode);
}
else if(root->rchild == NULL){ //右子树为空,左子树直接顶上去
BiTree tempNode = root;//临时指针
root = root->lchild;
free(tempNode);
}
else{ //左右子树都不为空
//一般的删除策略是左子树的最大数据 或 右子树的最小数据 代替要删除的节点(这里采用查找左子树最大数据来代替)
//
BiTree tempNode = root->lchild;
BiTree father = root;
while(tempNode->rchild!=NULL){//这里是while,上课讲的原理没问题
tempNode = tempNode->rchild;
father = tempNode;
}
root->key = tempNode->key;//传值给删除的节点,然后把下面的节点删掉
//DeleteNode(root->lchild,tempNode->key);
DeleteNode(father,tempNode->key);//直接从father(tempNode的父节点)开始删除temp(不直接删除tempNode是因为需要置fathe->child=NULL)
}
}
}

查找

顺序查找(线性查找)

顺序表和链表都适用

for(i=ST.TableLen-1;ST.elem[i]!=key;--i);

​ 循环条件是没有查找到目标元素,且从后往前找:多申请的[0]号位置保存目标key值,防止内存越界,若返回0代表没有找到。

折半查找(二分查找)

仅适用于有序的顺序表

哈希查找(散列查找)

散列函数:一个把查找表中的关键字映射成该关键字对应的地址的函数,记为Hash(key)=Addr

散列表:根据关键字而直接进行访问的数据结构

KMP字符串匹配

原理:首先找出目标字符串的“最长公共前后缀”,在当前字符串匹配失败时只需要移动使得前后缀匹配,而无须将标记回退。

排序

数据结构(动态申请数组)

typedef struct{
ElemType *elem;
int TableLen;
}SSTable;

交换排序

冒泡排序(BubbleSort)

for(i=0;i<n-1;i++)//i最多访问到8
{
flag=false;
for(j=n-1;j>i;j--)//把最小值就放在最前面
{
if(A[j-1]>A[j])
{
swap(A[j-1],A[j]);
flag=true;
}
}
if(false==flag)
return;
}

快速排序

分治的思想

QuickSort

void QuickSort(ElemType A[],int low,int high)
{
if(low<high)
{
int pivotpos=Partition(A,low,high);//分割点左边的元素都比分割点要小,右边的比分割点大
QuickSort(A,low,pivotpos-1);
QuickSort(A,pivotpos+1,high);
}
}

Partiton(挖坑法)——以左边为分隔值

int Partition(ElemType A[],int low,int high)
{
ElemType pivot=A[low];//把最左边的值暂存起来
while(low<high)
{
while(low<high&&A[high]>=pivot)//让high从最右边找,找到比分割值小,循环结束
--high;
A[low]=A[high];//先从右边找(最开始的坑在左边,先填上)
while(low<high&&A[low]<=pivot)//让low从最左边开始找,找到比分割值大,就结束
++low;
A[high]=A[low];
}
A[low]=pivot;
return low;
}

Partition(交换法)——以右边为分隔值

int Partition(int* arr, int left, int right)
{
int k, i;//k记录要放入比分割值小的数据的位置
for (i = left, k = left; i < right; i++)
{
if (arr[i] < arr[right])//i找到比分隔值小的值都会给k
{
swap(arr[k], arr[i]);//k遍历过的地方都是比分隔值小的值
k++;
}
}
swap(arr[k], arr[right]);//最后k的坐标就是分隔值
return k;
}

插入排序

直接插入排序(n^2)

在已经排好序的序列中从后往前遍历,寻找插入位置([0]位置可以存放哨兵——插入值,故循环条件是便利值大于或小于key,且一边比较一边移动元素)

for(i=2;i<=n;i++)//第零个元素是哨兵,从第二个元素开始拿,往前面插入
{
if(A[i]<A[i-1])
{
A[0]=A[i];//放到暂存位置,A[0]即是暂存,也是哨兵
for(j=i-1;A[0]<A[j];--j)//移动元素,内层循环控制有序序列中的每一个元素和要插入的元素比较
A[j+1]=A[j];
A[j+1]=A[0];//把暂存元素插入到对应位置
}
}

折半插入排序(n^2)

减少了比较次数,但没有减少数据移动次数。时间复杂度量级不变

for(i=2;i<=n;i++)
{
A[0]=A[i];
low=1;high=i-1;//low有序序列的开始,high有序序列的最后
while(low<=high)//先通过二分查找找到待插入位置
{
mid=(low+high)/2;
if(A[mid]>A[0])
high=mid-1;
else
low=mid+1;
}
for(j=i-1;j>=high+1;--j)
A[j+1]=A[j];
A[high+1]=A[0];
}

希尔排序

第一趟排序完成步长为n/2的插入排序,第二趟完成步长为n/4的插入排序,直到完成步长为1的插入排序

for(dk=n/2;dk>=1;dk=dk/2)//步长变化,步长变化
{
for(i=dk+1;i<=n;++i)//以dk为步长进行插入排序
{
if(A[i]<A[i-dk])
{
A[0]=A[i];
for(j=i-dk;j>0j=j-dk)
A[j+dk]=A[j];
A[j+dk]=A[0];
}
}
}

选择排序

简单选择排序

每一趟找到最小的交换即可,不需要每次都交换

for(i=0;i<n-1;i++)//最多可以为8
{
min=i;
for(j=i+1;j<n;j++)//j最多可以为9
{
if(A[j]<A[min])
min=j;
}
if(min!=i)
{
swap(A[i],A[min]);
}
}

堆排序

0号元素不参与排序(根节点下标为1,左节点为2i,右节点为2i+1)

HeapSort——排序
void HeapSort(ElemType A[],int len)
{
int i;
BuildMaxHeap(A,len);//建立大顶堆
for(i=len;i>1;i--)
{
swap(A[i],A[1]);//依次将大根堆的根(最大元素)放到末尾
AdjustDown(A,1,i-1);//重新调整根,使得仍然是大根堆
}
}
BuildMaxHeap——建立大根堆
void BuildMaxHeap(ElemType A[],int len)
{
for(int i=len/2;i>0;i--)//自下而上调整成大根堆(大根堆的调整需要子节点是大根堆),len/2是最后一个父节点
{
AdjustDown(A,i,len);
}
}
AdjustDown——调整子树(for实现)
void AdjustDown(ElemType A[],int k,int len)//调整A[k],此时除了A[k]其他节点(除了末尾已经排好序外的)均满足大根堆
{
int i;
A[0]=A[k];//A[0]只是中转(根节点下标为1)
for(i=2*k;i<=len;i*=2)//取左子节点
{
if(i<len&&A[i]<A[i+1])//左子节点与右子节点比较大小(取子节点中较大的那个)
i++;
if(A[0]>=A[i])//A[0]存的是A[k],如果当前节点已经比A[k]小,说明之后节点均比A[k]小,无需再调整
break;
else{
A[k]=A[i];//否则:给父节点赋值成子节点中较大的那个
k=i;//将子节点当做父节点继续考虑
}
}
A[k]=A[0];//将原来的根节点赋值给最后确定的节点
}

所有元素参与排序(根节点下标为0,左节点为2i+1,右节点为2i+2)

HeapSort1——排序
void HeapSort1(ElemType A[], int len)
{
int i;
//建立大顶堆
for (i = len / 2; i >= 0; i--)//这里不需要从len/2开始遍历,最大的父节点应该是(len-1)/2
{
AdjustDown1(A, i, len);
}
//开始排序
swap(A[0], A[len]);//交换顶部和数组最后一个元素
for (i = len - 1; i > 0; i--)
{
AdjustDown1(A, 0, i);//剩下元素调整为大根堆
swap(A[0], A[i]);
}
}
AdjustDown1——调整子树(while实现)
void AdjustDown1(ElemType A[], int k, int len)
{
int dad = k;
int son = 2 * dad + 1; //左孩子下标
while (son<=len)
{
if (son + 1 <= len && A[son] < A[son + 1])//看下有没有右孩子,比较左右孩子选大的
{
son++;
}
if (A[son] > A[dad])//比较孩子和父亲
{
swap(A[son], A[dad]);
dad = son;
son = 2 * dad + 1;
}
else {
break;
}
}
}

归并排序

先递归分隔——MergeSort

void MergeSort(ElemType A[],int low,int high)//递归分割
{
if(low<high)
{
int mid=(low+high)/2;
MergeSort(A,low,mid);
MergeSort(A,mid+1,high);
Merge(A,low,mid,high);
}
}

再归并排序——Merge

void Merge(ElemType A[],int low,int mid,int high)
{
ElemType B[N];
int i,j,k;
for(k=low;k<=high;k++)//复制元素到B中
B[k]=A[k];
for(i=low,j=mid+1,k=i;i<=midk++)//合并两个有序数组
{
if(B[i]<=B[j])
A[k]=B[i++];
else
A[k]=B[j++];
}
while(i<=mid)//如果有剩余元素,接着放入即可
A[k++]=B[i++];
while(j<=high)
A[k++]=B[j++];
}

图的遍历——邻接表

##数据结构及宏定义

三层结构体:第一层是图,保存了边的数目和点的数目,以及N个节点的信息指针;第二层是节点,保存了节点的名字,以及其指向的所有节点(中的第一个节点的地址);第三层即链表中的节点。

#define MAX 100
#define isLetter(a) ((((a)>='a')&&((a)<='z')) || (((a)>='A')&&((a)<='Z')))
#define LENGTH(a) (sizeof(a)/sizeof(a[0]))

// 邻接表中表对应的链表的顶点
typedef struct _ENode
{
int ivex; // 该边所指向的顶点的位置,是数组的下标
struct _ENode *next_edge; // 指向下一条弧的指针
}ENode, *PENode;

// 邻接表中表的顶点
typedef struct _VNode
{
char data; // 顶点信息(顶点名字)
ENode *first_edge; // 指向第一条依附该顶点的弧
}VNode;

// 邻接表
typedef struct _LGraph
{
int vexnum; // 图的顶点的数目
int edgnum; // 图的边的数目
VNode vexs[MAX];
}LGraph;

主函数main()

void main()
{
LGraph* pG;

// 无向图自定义"图"(自己输入数据,输入的方法可以参考create_example_lgraph初始化好的数据)
pG = create_lgraph();
//// 无向图的创建,采用已有的"图"
pG = create_example_lgraph();
//有向图的创建
pG = create_example_lgraph_directed();
// 打印图
print_lgraph(*pG);//打印邻接表图
DFSTraverse(*pG);//深度优先遍历
BFS(*pG);//广度优先遍历
system("pause");
}

一些工具函数

###get_position()——返回ch在matrix矩阵中的位置

用于给定顶点的名字(如"a"),返回顶点对应的下标(如1),因为输入是面向显示的,内部运算是面向机器的,需要有name到数据的转变。

不过一般的题目中名字就是下标,不会考察转换。

static int get_position(LGraph g, char ch)
{
int i;
for(i=0; i<g.vexnum; i++)//去顶点结构体数组中遍历每个顶点
if(g.vexs[i].data==ch)
return i;//返回的是对应顶点的下标
return -1;
}

read_char()——读取一个输入字符

是考虑到输入时都是字符和空格,挨个跳过太麻烦,所以循环跳过空格,只读字母。

static char read_char()
{
char ch;

do {
ch = getchar();
} while(!isLetter(ch));

return ch;
}

link_last()——将node链接到list的末尾

static void link_last(ENode *list, ENode *node)
{
ENode *p = list;

while(p->next_edge)
p = p->next_edge;
p->next_edge = node;
}

create_lgraph()——根据输入数据创建图

返回值即为该图的地址

基本步骤:

①给pG清零(将所有顶点的指针指向NULL,虽然均会被覆盖)

②输入顶点数和边数,存入pG

③循环读入顶点信息(名字,如下标1的节点名字为a),并给每个节点的first_edge置为NULL(因为还没有读入边,此时默认图里无边)

④循环读入边的信息,将顶点名字转化为下标(大多数题目中下标即名字,这部分可以忽略)。申请两个node:(1)清零(置NULL)。(2)指向刚输入的节点下标。(3)分情况插入(考虑是不是插入的第一个节点)

LGraph* create_lgraph()
{
char c1, c2;
int v, e;
int i, p1, p2;
ENode *node1, *node2;
LGraph* pG;

// 输入"顶点数"和"边数"
printf("input vertex number: ");
scanf("%d",
printf("input edge number: ");
scanf("%d",
if ( v < 1 || e < 1 || (e > (v * (v-1))))
{
printf("input error: invalid parameters!\n");
return NULL;
}

if ((pG=(LGraph*)malloc(sizeof(LGraph))) == NULL )
return NULL;
memset(pG, 0, sizeof(LGraph));

// 初始化"顶点数"和"边数"
pG->vexnum = v;
pG->edgnum = e;
// 初始化"邻接表"的顶点
for(i=0; i<pG->vexnum; i++)
{
printf("vertex(%d): ", i);
pG->vexs[i].data = read_char();
pG->vexs[i].first_edge = NULL;
}

// 初始化"邻接表"的边
for(i=0; i<pG->edgnum; i++)
{
// 读取边的起始顶点和结束顶点
printf("edge(%d): ", i);
c1 = read_char();
c2 = read_char();

//输入的是名字,所以要根据名字找下标
p1 = get_position(*pG, c1);
p2 = get_position(*pG, c2);

// 初始化node1
node1 = (ENode*)calloc(1,sizeof(ENode));
node1->ivex = p2;
// 将node1链接到"p1所在链表的末尾"
if(pG->vexs[p1].first_edge == NULL)
pG->vexs[p1].first_edge = node1;
else
link_last(pG->vexs[p1].first_edge, node1);
// 初始化node2
node2 = (ENode*)calloc(1,sizeof(ENode));
node2->ivex = p1;
// 将node2链接到"p2所在链表的末尾"
if(pG->vexs[p2].first_edge == NULL)
pG->vexs[p2].first_edge = node2;
else
link_last(pG->vexs[p2].first_edge, node2);
}

return pG;
}

create_example_lgraph()——用内置的数据创建无向图

LGraph* create_example_lgraph()
{
char c1, c2;
char vexs[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
char edges[][2] = {
{'A', 'C'},
{'A', 'D'},
{'A', 'F'},
{'B', 'C'},
{'C', 'D'},
{'E', 'G'},
{'F', 'G'}};
int vlen = LENGTH(vexs);
int elen = LENGTH(edges);
//上面类似一个邻接矩阵存储
int i, p1, p2;
ENode *node1, *node2;
LGraph* pG;//pG表示图


if ((pG=(LGraph*)malloc(sizeof(LGraph))) == NULL )
return NULL;
memset(pG, 0, sizeof(LGraph));//就是把申请的空间内初始化为零

// 初始化"顶点数"和"边数"
pG->vexnum = vlen;
pG->edgnum = elen;
// 初始化"邻接表"的顶点
for(i=0; i<pG->vexnum; i++)
{
pG->vexs[i].data = vexs[i];
pG->vexs[i].first_edge = NULL;
}

// 初始化"邻接表"的边
for(i=0; i<pG->edgnum; i++)
{
// 读取边的起始顶点和结束顶点
c1 = edges[i][0];
c2 = edges[i][1];

p1 = get_position(*pG, c1);//p1对应起始顶点下标位置
p2 = get_position(*pG, c2);//p1对应结束顶点下标位置

// 初始化node1
node1 = (ENode*)calloc(1,sizeof(ENode));
node1->ivex = p2;
// 将node1链接到"p1所在链表的末尾"
if(pG->vexs[p1].first_edge == NULL)
pG->vexs[p1].first_edge = node1;
else
link_last(pG->vexs[p1].first_edge, node1);
// 初始化node2
node2 = (ENode*)calloc(1,sizeof(ENode));
node2->ivex = p1;
// 将node2链接到"p2所在链表的末尾"
if(pG->vexs[p2].first_edge == NULL)
pG->vexs[p2].first_edge = node2;
else
link_last(pG->vexs[p2].first_edge, node2);
}

return pG;
}

create_example_lgraph_directed()——用内置的数据创建有向图

LGraph* create_example_lgraph_directed()
{
char c1, c2;
char vexs[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
char edges[][2] = {
{'A', 'B'},
{'B', 'C'},
{'B', 'E'},
{'B', 'F'},
{'C', 'E'},
{'D', 'C'},
{'E', 'B'},
{'E', 'D'},
{'F', 'G'}};
int vlen = LENGTH(vexs);
int elen = LENGTH(edges);
int i, p1, p2;
ENode *node1;
LGraph* pG;


if ((pG=(LGraph*)malloc(sizeof(LGraph))) == NULL )
return NULL;
memset(pG, 0, sizeof(LGraph));

// 初始化"顶点数"和"边数"
pG->vexnum = vlen;
pG->edgnum = elen;
// 初始化"邻接表"的顶点
for(i=0; i<pG->vexnum; i++)
{
pG->vexs[i].data = vexs[i];
pG->vexs[i].first_edge = NULL;
}

// 初始化"邻接表"的边
for(i=0; i<pG->edgnum; i++)
{
// 读取边的起始顶点和结束顶点
c1 = edges[i][0];
c2 = edges[i][1];

p1 = get_position(*pG, c1);
p2 = get_position(*pG, c2);
// 初始化node1
node1 = (ENode*)calloc(1,sizeof(ENode));
node1->ivex = p2;
// 将node1链接到"p1所在链表的末尾"
if(pG->vexs[p1].first_edge == NULL)
pG->vexs[p1].first_edge = node1;
else
link_last(pG->vexs[p1].first_edge, node1);
}

return pG;
}

print_lgraph()——打印邻接表图

void print_lgraph(LGraph G)
{
int i;
ENode *node;

printf("List Graph:\n");
for (i = 0; i < G.vexnum; i++)//遍历所有的顶点
{
printf("%d(%c): ", i, G.vexs[i].data);
node = G.vexs[i].first_edge;
while (node != NULL)//把每个顶点周围的结点都输出一下
{
printf("%d(%c) ", node->ivex, G.vexs[node->ivex].data);
node = node->next_edge;
}
printf("\n");
}
}

DFSTraverse()——深度优先搜索遍历图

无脑DFS

DFSTraverse()相当于是一个起点函数,定义标记数组然后初始化,接着直接循环DFS嵌套就行

static void DFS(LGraph G, int i, int *visited)
{
ENode *node;

visited[i] = 1;//要访问当前结点了,所以打印
printf("%c ", G.vexs[i].data);
node = G.vexs[i].first_edge;//拿当前顶点的后面一个顶点
while (node != NULL)
{
if (!visited[node->ivex])//只要对应顶点没有访问过,深入到下一个顶点访问
DFS(G, node->ivex, visited);
node = node->next_edge;//某个顶点的下一条边,例如B结点的下一条边
}
}

/*
* 深度优先搜索遍历图
*/

void DFSTraverse(LGraph G)
{
int i;
int visited[MAX]; // 顶点访问标记

// 初始化所有顶点都没有被访问
for (i = 0; i < G.vexnum; i++)
visited[i] = 0;

printf("DFS: ");
//从A开始深度优先遍历
for (i = 0; i < G.vexnum; i++)
{
if (!visited[i])
DFS(G, i, visited);
}
printf("\n");
}

广度优先搜索(类似于树的层次遍历)

开个队列就完了

void BFS(LGraph G)
{
int head = 0;
int rear = 0;
int queue[MAX]; // 辅组队列
int visited[MAX]; // 顶点访问标记
int i, j, k;
ENode *node;

//每个顶点未被访问
for (i = 0; i < G.vexnum; i++)
visited[i] = 0;
//从零号顶点开始遍历
printf("BFS: ");
for (i = 0; i < G.vexnum; i++)//对每个连同分量均调用一次BFS
{
if (!visited[i])//如果没访问过,就打印,同时入队,最初是A
{
visited[i] = 1;//标记已经访问过
printf("%c ", G.vexs[i].data);
queue[rear++] = i; // 入队列
}
while (head != rear) //第一个进来的是A,遍历A的每一条边
{
j = queue[head++]; // 出队列
node = G.vexs[j].first_edge;
while (node != NULL)
{
k = node->ivex;
if (!visited[k])
{
visited[k] = 1;
printf("%c ", G.vexs[k].data);
queue[rear++] = k;//类似于树的层次遍历,遍历到的同时入队
}
node = node->next_edge;
}
}
}
printf("\n");
}
举报

相关推荐

0 条评论