考研笔记——王道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>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