其他分享
首页 > 其他分享> > 考研笔记——王道C语言

考研笔记——王道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;
}
L-mallocNULLL-mallocNULLs->data=xL-mallocs->data=xNULL

尾插法新建链表

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

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

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;
}
L-malloc,r未初始化L-malloc,r,r指向新节点s->data=x未初始化L-mallocs->data=x,r,r变为新节点未初始化

双链表(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>0&&A[0]<A[j];j=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<=mid&&j<=high;k++)//合并两个有序数组
	{
		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", &v);
    printf("input edge number: ");
    scanf("%d", &e);
    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");
}

标签:结点,return,int,C语言,王道,pG,NULL,节点,考研
来源: https://blog.csdn.net/qq_35653247/article/details/123591725