C语言知识点补充(链表和队列)
文章目录
- 单向链表总结快速一览
- 链表
- 链表创建
- 链表遍历
- 链表的释放
- 链表的查找
- 节点的删除
- 节点的插入
- 链表的排序
- 双向链表
- 队列
- 链队列
- 链表初始化
- 链表入队
- 链表出队
单向链表总结快速一览
链表创建:链表为空,该节点变为根节点,否则while循环到末尾加入新节点,for循环一个一个节点加入
链表遍历:while循环到末尾(末尾指向的NULL)
链表释放:判断根节点是否空,从根节点的地址一个一个释放,同样根节点要指向下一个节点
链表查找:和查找同理,while循环迭代
节点删除:看通过数据域还是指针域判断删除哪个节点,分为链表为空,不操作;删除根节点,下一节点变根节点;删除中间元素,迭代寻找中要保存上下节点地址,方便删除后重建链表。这里刚开始上下节点地址变量存放的都是根节点,这样根节点和中间元素删除就统一在一起了。
节点插入:同理分为链表为空还是,插入根节点,插入中间等
链表
操作系统里面因为用到非常多
链表分为单向链表和双向链表,使用最多的是双向链表
链表是一种物理存储上非连续,但通过内部指针链接次序,实现可以线性访问的结构。
链表:节点,动态生成(malloc)
节点包括:存储数据的数据域、存储下一节点指针的指针域
typedef struct student{int num;char name[20];struct student *next;}STU;
双向链表就是节点里有两个指针,分别指向前一节点和后一节点
链表是通过节点把离散的数据链接成一个表,通过对节点的插入和删除操作从而实现对数据的存取。而数组是通过开辟一段连续的内存来存储数据,这是数组和链表最大的区别。数组有起始地址和结束地址,而链表是一个圈,没有头和尾之分, 但是为了方便节点的插入和删除操作会人为的规定一个根节点。
链表创建
整体思想是,一个单向的链表,第一个根节点是通过看是不是NULL指针来判断,后面节点的添加是通过while循环到尾部,加入新节点
typedef struct student
{
//数据域
int score;
....//指针域
sturct student *next;
}STU;void link_creat_head(STU **p_head,STU *p_new) //使用双重指针是因为需要修改根节点的地址
{STU *p_mov = *p_head;if(*p_head == NULL) //当第一次加入链表为空时,head执行p_new{*p_head = p_new;p_new->next=NULL;}else //第二次及以后加入链表{while(p_mov->next!=NULL){p_mov=p_mov->next; //找到原有链表的最后一个节点}p_mov->next = p_new; //将新申请的节点加入链表p_new->next = NULL;}
}int main()
{STU *head = NULL,*p_new = NULL;int num,i;printf("请输入链表初始个数:\n");scanf("%d",&num);for(i = 0; i < num;i++){p_new = (STU*)malloc(sizeof(STU));//申请一个新节点printf("请输入学号、分数、名字:\n"); //给新节点赋值scanf("%d %d %s",&p_new->num,&p_new->score,p_new->name);link_creat_head(&head,p_new); //将新节点加入链表}
}
链表遍历
//链表的遍历
void link_print(STU *head)
{STU *p_mov;//定义新的指针保存链表的首地址,防止使用head改变原本链表p_mov = head;//当指针保存最后一个结点的指针域为NULL时,循环结束while(p_mov!=NULL){//先打印当前指针保存结点的指针域printf("num=%d score=%d name:%s\n",p_mov->num,\p_mov->score,p_mov->name);//指针后移,保存下一个结点的地址p_mov = p_mov->next;}
}
链表的释放
重新定义一个指针q,保存p指向节点的地址,然后p后移保存下一个节点的地址,然后释放q对应的节点
//链表的释放,这里是从根节点全部释放void link_free(STU **p_head){//定义一个指针变量保存头结点的地址STU *pb=*p_head; while(*p_head!=NULL){//先保存p_head指向的结点的地址pb=*p_head;//p_head保存下一个结点地址*p_head=(*p_head)‐>next;//释放结点并防止野指针free(pb);pb = NULL;}}
链表的查找
//链表的查找
//按照学号查找
STU * link_search_num(STU *head,int num)
{STU *p_mov;//定义的指针变量保存第一个结点的地址p_mov=head;//当没有到达最后一个结点的指针域时循环继续while(p_mov!=NULL){//如果找到是当前结点的数据,则返回当前结点的地址if(p_mov->num == num)//找到了{return p_mov;}//如果没有找到,则继续对比下一个结点的指针域p_mov=p_mov->next;}//当循环结束的时候还没有找到,说明要查找的数据不存在,返回NULL进行标识return NULL;//没有找到
}//按照姓名查找
STU * link_search_name(STU *head,char *name)
{STU *p_mov;p_mov=head;while(p_mov!=NULL){if(strcmp(p_mov->name,name)==0)//找到了,string compare{return p_mov;}p_mov=p_mov->next;}return NULL;//没有找到
}
节点的删除
如果链表为空,不需要删除;
如果删除的是第一个结点,则需要将链表首地址的指针保存第一个结点的下一个结点的地址;
如果删除的是中间结点,则找到中间结点的前一个结点,让前一个结点的指针域保存这个结点的后一个结点的地址即可;
//链表结点的删除
void link_delete_num(STU **p_head,int num)
{STU *pb,*pf; //前一个节点和后一个节点pb=pf=*p_head;if(*p_head == NULL)//链表为空,不用删{printf("链表为空,没有您要删的节点");\return ;}while(pb->num != num && pb->next !=NULL)//循环找,要删除的节点{pf=pb;pb=pb->next;}if(pb->num == num)//找到了一个节点的num和num相同{if(pb == *p_head)//要删除的节点是头节点{//让保存头结点的指针保存后一个结点的地址*p_head = pb->next;}else{//前一个结点的指针域保存要删除的后一个结点的地址pf->next = pb->next;}//释放空间free(pb);pb = NULL; //重要!!!}else//没有找到{printf("没有您要删除的节点\n");}
}
节点的插入
这里按学号顺序插入,不是指定位置插入,实际中也没有什么意义,指定位置插入
//链表的插入:按照学号的顺序插入
void link_insert_num(STU **p_head,STU *p_new)
{STU *pb,*pf;pb=pf=*p_head;if(*p_head ==NULL)// 链表为空链表{*p_head = p_new;p_new->next=NULL;return ;}while((p_new->num >= pb->num) && (pb->next !=NULL) ){pf=pb;pb=pb->next;}if(p_new->num < pb->num)//找到一个节点的num比新来的节点num大,插在pb的前面{if(pb== *p_head)//找到的节点是头节点,插在最前面{p_new->next= *p_head;*p_head =p_new;}else{pf->next=p_new;p_new->next = pb;}}else//没有找到pb的num比p_new->num大的节点,插在最后{pb->next =p_new;p_new->next =NULL;}
}
链表的排序
如果链表为空,不需要排序。
如果链表只有一个结点,不需要排序。
先将第一个结点与后面所有的结点依次对比数据域,只要有比第一个结点数据域小的,则交 换位置。
交换之后,拿新的第一个结点的数据域与下一个结点再次对比,如果比他小,再次交换,依 次类推。
第一个结点确定完毕之后,接下来再将第二个结点与后面所有的结点对比,直到最后一个结 点也对比完毕为止。
//链表的排序
void link_order(STU *head)
{STU *pb,*pf,temp;pf=head;if(head==NULL){printf("链表为空,不用排序\n");return ;}if(head->next ==NULL){printf("只有一个节点,不用排序\n");return ;}while(pf->next !=NULL)//以pf指向的节点为基准节点,{pb=pf->next;//pb从基准元素的下个元素开始while(pb!=NULL){if(pf->num > pb->num){temp=*pb;*pb=*pf;*pf=temp;temp.next=pb->next;pb->next=pf->next;pf->next=temp.next;}pb=pb->next;}pf=pf->next;}
}
双向链表
类似的,创建一个节点作为头节点,将两个指针域都保存NULL;先找到链表中的最后一个节点,然后让最后一个节点的指针域保存新插入节点的地址,新插入节点的两个指针域,一个保存上一个节点的地址,一个保存NULL;
#include <stdio.h>
#include <stdlib.h>//定义结点结构体
typedef struct student
{//数据域int num; //学号int score; //分数char name[20]; //姓名//指针域struct student *front; //保存上一个结点的地址struct student *next; //保存下一个结点的地址
}STU;void double_link_creat_head(STU **p_head,STU *p_new)
{STU *p_mov=*p_head;if(*p_head==NULL) //当第一次加入链表为空时,head执行p_new{*p_head = p_new;p_new->front = NULL;p_new->next = NULL;}else //第二次及以后加入链表{while(p_mov->next!=NULL){p_mov=p_mov->next; //找到原有链表的最后一个节点}p_mov->next = p_new; //将新申请的节点加入链表p_new->front = p_mov;p_new->next = NULL;}
}void double_link_print(STU *head)
{STU *pb;pb=head;while(pb->next!=NULL){printf("num=%d score=%d name:%s\n",pb->num,pb->score,pb->name);pb=pb->next;}printf("num=%d score=%d name:%s\n",pb->num,pb->score,pb->name);printf("***********************\n");while(pb!=NULL){printf("num=%d score=%d name:%s\n",pb->num,pb->score,pb->name);pb=pb->front;}
}int main()
{STU *head=NULL,*p_new=NULL;int num,i;printf("请输入链表初始个数:\n");scanf("%d",&num);for(i=0;i<num;i++){p_new=(STU*)malloc(sizeof(STU));//申请一个新节点printf("请输入学号、分数、名字:\n"); //给新节点赋值scanf("%d %d %s",&p_new->num,&p_new->score,p_new->name);double_link_creat_head(&head,p_new); //将新节点加入链表}double_link_print(head);
}
队列
栈只允许在一端进行插入或删除的线性表,所以是后进先出(Last In First Out)LIFO
队列只允许在一端进行插入操作,在另一端进行删除操作,先进先出(First In First Out)的线性表,简称FIFO
队列的顺序实现是指分配一块连续的存储单元存放队列中的元素,并附设两个指针:队头指针 front指向队头元素,队尾指针 rear 指向队尾元素的下一个位置。
#define MAXSIZE 50 //定义队列中元素的最大个数
typedef struct{ElemType data[MAXSIZE]; //存放队列元素int front,rear;
}SqQueue;
为了防止上溢出,就是队头指针指向了队尾,所以要构成循环
队列初始化:
/*初始化一个空队列Q*/
Status InitQueue(SqQueue *Q){Q->front = 0;Q->rear = 0;return OK;
}
循环队列判队空:
/*判队空*/
bool isEmpty(SqQueue Q){if(Q.rear == Q.front){return true;}else{return false;}
}
循环队列的长度
/*返回Q的元素个数,也就是队列的当前长度*/
int QueueLength(SqQueue Q){return (Q.rear - Q.front + MAXSIZE) % MAXSIZE;
}
循环队列入队:
/*若队列未满,则插入元素e为Q新的队尾元素*/
Status EnQueue(SqQueue *Q, ElemType e){if((Q->rear + 1) % MAXSIZE == Q->front){return ERROR; //队满}Q->data[Q->rear] = e; //将元素e赋值给队尾Q->rear = (Q->rear + 1) % MAXSIZE; //rear指针向后移一位置,若到最后则转到数组头部return OK;
}
循环队列出队
/*若队列不空,则删除Q中队头元素,用e返回其值*/
Status DeQueue(SqQueue *Q, ElemType *e){if(isEmpty(Q)){return REEOR; //队列空的判断}*e = Q->data[Q->front]; //将队头元素赋值给eQ->front = (Q->front + 1) % MAXSIZE; //front指针向后移一位置,若到最后则转到数组头部
}
链队列
用链式存储结构
/*链式队列结点*/
typedef struct {ElemType data;struct LinkNode *next;
}LinkNode;
/*链式队列*/
typedef struct{LinkNode *front, *rear; //队列的队头和队尾指针
}LinkQueue;
当Q->front == NULL 并且 Q->rear == NULL 时,链队列为空。
链表初始化
void InitQueue(LinkQueue *Q){Q->front = Q->rear = (LinkNode)malloc(sizeof(LinkNode)); //建立头结点Q->front->next = NULL; //初始为空
}
链表入队
Status EnQueue(LinkQueue *Q, ElemType e){LinkNode s = (LinkNode)malloc(sizeof(LinkNode));s->data = e;s->next = NULL;Q->rear->next = s; //把拥有元素e新结点s赋值给原队尾结点的后继Q->rear = s; //把当前的s设置为新的队尾结点return OK;
}
链表出队
/*若队列不空,删除Q的队头元素,用e返回其值,并返回OK,否则返回ERROR*/
Status DeQueue(LinkQueue *Q, Elemtype *e){LinkNode p;if(Q->front == Q->rear){return ERROR;}p = Q->front->next; //将欲删除的队头结点暂存给p*e = p->data; //将欲删除的队头结点的值赋值给eQ->front->next = p->next; //将原队头结点的后继赋值给头结点后继//若删除的队头是队尾,则删除后将rear指向头结点if(Q->rear == p){ Q->rear = Q->front;}free(p);return OK;
}