带头结点的线性链表的基本操作
来源:互联网 发布:百度免费空间域名 编辑:程序博客网 时间:2024/05/11 17:57
持续了好久,终于有了这篇博客,链表的操作需要借助图像模型进行反复学习,这里尽可能的整理并记录下自己的思考,以备后面复习,和大家分享。需要说明的是,我们从实际应用角度出发重新定义了线性表。
一. 定义
从上一篇文章可以看到,由于链表在空间的合理利用上和插入、删除时不需要移动等优点,因此在很多场合下,它是线性表的首选存储结构。然而,它也存在某些实现的缺点,如求线性表的长度时不如顺序存储结构的缺点。因此,从实际应用角度出发重新定义了线性表。
一个带头结点的线性链表类型定义如下:
//结点类型typedef struct Node{ ElemType data; struct Node *next;}Node,*PNode;//链表类型typedef struct List{ PNode head; //指向线性链表的头结点 PNode tail; //指向线性链表的尾结点 size_t size; //线性表中数据元素的个数}List;
二、函数实现的难点
我们基于一个改良的链表类型,进行常见的基本操作,这里将就每个函数实现的重难点找出来,使其核心精髓展现在我们眼前:
InitList(List *list)
该函数实现了将我们定义的链表进行初始化,使其头指针、尾指针都指向头结点(空表),链表的大小size为0.
void push_back(List *list,ElemType x)
尾插入,首先构建一个节点,然后将其插入链表尾部。此时体现出来该种数据结构的优势,我们无需从链表首部直接操作,直接找到了链表尾部对其操作。
void push_front(List *list,ElemType x)
头插,核心是用于在头结点和第一个结点之间插入新的子节点。当然,如果插入前为空表,需修改链表的尾结点指向第一个结点。(因为是对头部操作,我们需要留意尾结点)
void pop_back(List *list)
尾部删除,首先判断链表是否非空,然后不停的删除尾结点。当然,需要找到尾结点的前驱,我们需要顺序遍历。
void pop_front(List *list)
头部删除,首先判断链表是否非空,然后不停的删除头结点第一个结点。需要注意,如果删除到只剩头结点时,需要将尾指针指向头结点。(因为是对头部操作,我们需要留意尾结点)
void insert_val(List *list, ElemType x)
插入值,也即插入一个结点。前提是有序,按值插入(需考虑插入到尾结点之后情况:此时更新尾结点指针)。
Node* find (List *list, ElemType key)void delete_val(List *list,ElemType key)
删除值,调用了find方法,我们首先找到指向当前要删除值的结点,然后将下一结点的值赋值给当前结点,使当前结点指向下一结点的next,删除下一结点,从而变相的删除了当前结点。
当然,我们也可以通过顺序遍历,找到当前要删除值结点的前驱,然后删除该节点。
void sort(List *list)
本篇文章的重点:排序。之前的实现中,我们都是通过某种排序方法,交换结点中的data,从而实现。本篇文章我们通过交换结点的方法来实现。
首先,我们把整个单链表断开,已排序部分仅仅包括头结点和第一个结点(尾指针指向第一个结点),待排序部分从第二个结点开始余下的部分。
我们每次从待排序部分取出一个结点s,将其值和已排序部分顺序比较,从而找到要插入位置的前驱p,插入前要更新q为待排序链表的第一个结点。然后尾插即可。
void reverse(List *list)
本篇文章的重点:反转。同上,我们还是把整个单链表断开分为两部分:已排序部分仅仅包括头结点和第一个结点(尾指针指向第一个结点),待排序部分从第二个结点开始余下的部分。
我们每次从待排序部分取出一个结点给p,然后在头结点和第一个结点之间头插实现。
三、代码
sList.h
#ifndef SLIST_H__#define SLIST_H__#include <stdio.h>#include <malloc.h>#include <assert.h>typedef int ElemType;//结点类型typedef struct Node{ ElemType data; struct Node *next;}Node,*PNode;//链表类型typedef struct List{ PNode head; PNode tail; size_t size;}List;void InitList(List *list);void push_back(List *list,ElemType x); //尾插void push_front(List *list,ElemType x); //头插void pop_back(List *list); //尾删void pop_front(List *list); //头删void insert_val(List *list, ElemType x); //按值插入Node* find (List *list, ElemType key);//查找某值int length(List *list); //长度void delete_val(List *list,ElemType key); //删除值void sort(List *list);//升序void reverse(List *list);//反转void clear(List *list);//清除void destroy(List *list);//销毁void show_list(List *list);#endif // SLIST_H__
sList.cpp
#include "sList.h"void InitList(List *list){ list->head = list->tail = (Node *)malloc(sizeof(Node)); assert(list->head != NULL); list->head->next = NULL; //头结点下一个为空 list->size = 0;}//1.更新新尾结点(一直对尾结点操作,故不需考虑)void push_back(List *list,ElemType x){ Node *s = (Node *)malloc(sizeof(Node)); assert (s != NULL); s->data = x; s->next = list->tail->next; list->tail->next = s; //原尾结点指向s list->tail = s;//更新为新尾结点 //由于一直对尾指针考虑,因此这里不需要考虑size=0 list->size++;}//1.在头结点之后插入作为第一个结点(需要考虑尾结点)void push_front(List *list,ElemType x){ Node *s = (Node *)malloc(sizeof(Node)); assert (s != NULL); s->data = x; s->next = list->head->next; //s结点指向第一个结点 list->head->next = s; //头结点指向第一个结点 //插入时,如果是第一个结点(size=0)则需修改list->tail指针指向s if(list->size == 0) { list->tail = s; } list->size++;}//1.判断非空 2.使倒数第二个结点为新尾结点(一直对尾结点操作,故不需考虑)void pop_back(List *list){ if(list->size == 0) { printf("error: seqList is empty\n"); return; } Node *p = list->head; while (p->next != list->tail) { p = p->next; } free(list->tail);//删除旧尾结点 list->tail = p;//新尾结点 list->tail->next = NULL;//新尾结点next为NULL list->size--;}//1.判断非空 2.删除头结点之后第一个结点 3.需要考虑尾结点void pop_front(List *list){ if(list->size == 0) { printf("error: seqList is empty\n"); return; } Node *q = list->head->next; list->head->next = q->next; free(q); //删除时,如果只剩第一个结点(size=1)则需修改list->tail指针指向头结点 if(list->size == 1) { list->tail = list->head; } list->size--;}//前提:有序。按值插入(考虑插入到尾结点之后情况)void insert_val(List *list, ElemType x){ Node *s = (Node *)malloc(sizeof(Node)); assert(s != NULL); s->data = x; s->next = NULL; Node *p = list->head; while(p->next != NULL && p->next->data <x) p=p->next; //若插入到尾结点,则更新尾结点 if(p->next == NULL) { list->tail = s; } //找到该插入位置的前一位 s->next = p->next; p->next = s; //指向新创建的结点 list->size++;}Node* find (List *list, ElemType key){ Node *p = list->head->next; //利用短路条件,注意顺序 while( p!=NULL && p->data != key) { p = p->next; } return p;}int length(List *list){ return list->size;}void delete_val(List *list,ElemType key){ if(list->size == 0) return; Node *p = find(list,key); if(p == NULL) { printf("delete val not exist\n"); return ; } if(p == list->tail) { pop_back(list); }else { //将要删除数据的next拿过来替换值,取出next的有用信息后free掉q Node *q = p->next; p->data = q->data; p->next = q->next; free(q); list->size--; }}//把整个单链表断开,把剩下链表的结点根据值升序尾插void sort(List *list){ if(list->size==0 || list->size == 1) return ; Node *s = list->head->next; //指向第一个结点 Node *q = s->next;//指向第二个结点 list->tail = s; list->tail->next = NULL; //断开链表 //按值插入 while(q!=NULL) { s = q; q = q->next; //p指针为已排序指针,s为待排序指针 Node *p = list->head; while(p->next != NULL && p->next->data < s->data) p=p->next; //若插入到尾结点,则更新尾结点 if(p->next == NULL) { list->tail = s; } //尾插 s->next = p->next; p->next = s; //指向新创建的结点 }}//把整个单链表断开,把剩下链表的结点按值头插void reverse(List *list){ if(list->size==0 || list->size == 1) return ; Node *p = list->head; //指向第一个结点 Node *q = p->next;//指向第二个结点 list->tail = p;//指向第一个结点 list->tail->next = NULL; //断开链表 while(q != NULL) { p = q; q = q->next; p->next = list->head->next ; list->head->next = p; //在头结点和第一个结点直接插入 }}void clear(List *list){ if(list->size ==0) return ; Node *p = list->head->next; while(p!=NULL) { list->head->next = p->next; free(p); p = list->head->next; } list->tail = list->head; list->size = 0;}void destroy(List *list){ clear(list); free(list->head); list->tail = list->head =NULL;}void show_list(List *list){ Node *p = list->head->next; while(p) { printf("%d--->",p->data); p = p->next; } printf("Nul\n");}
main.cpp
#include "sList.h"/*break语句通常用在循环语句和开关语句中。当break用于开关语句switch中时,可使程序跳出switch而执行switch以后的语句;如果没有break语句,则会从满足条件的地方(即与switch(表达式)括号中表达式匹配的case)开始执行,直到switch结构结束。当break语句用于do-while、for、while循环语句中时,可使程序终止循环。而执行循环后面的语句,通常break语句总是与if语句联在一起。即满足条件时便跳出循环。*/int main(){ List mylist; InitList(&mylist); int select; ElemType num; Node *p; while(select) { printf("* [1] push_back [2] push_front *\n"); printf("* [3] show_list [4] pop_back *\n"); printf("* [5] pop_front [6] insert_val *\n"); printf("* [7] find [8] length *\n"); printf("* [9] delete_val [10] sort *\n"); printf("* [11] reverse [12] clear *\n"); printf("* [13] destroy [0] quit_system *\n"); printf("please choose:>"); scanf("%d",&select); if(select == 0) break; switch(select) { case 1: printf("push_back: input numbers(-1 erminate):"); while(scanf("%d",&num),num != -1) { push_back(&mylist,num); //尾插 } break; case 2: printf("push_front: input numbers(-1 erminate):"); while(scanf("%d",&num),num != -1) { push_front(&mylist,num); //头插 } break; case 3: show_list(&mylist); break; case 4: pop_back(&mylist); //尾删 break; case 5: pop_front(&mylist); //头删 break; case 6: printf("input inset value:>"); scanf("%d",&num); insert_val(&mylist,num); //升序插值 break; case 7: printf("input find value:>"); scanf("%d",&num); p = find(&mylist,num); //查找某值 if(p == NULL) printf("value %d not exist\n",num); break; case 8: printf("length:%d\n",length(&mylist)); //长度 case 9: printf("input delete value:>"); scanf("%d",&num); delete_val(&mylist,num); //删除值 break; case 10: sort(&mylist); //升序 break; case 11: reverse(&mylist); //反转 break; case 12: clear(&mylist); //清除 break; case 13: destroy(&mylist); //销毁 break; default: printf("error,choose again\n"); break; } } printf("Hello world!"); return 0;}
- 带头结点的线性链表的基本操作
- 带头结点的链表基本操作
- 带头结点的线性链表类型
- 带头结点的线性链表类型
- 带头结点的线性链表类型
- 带头结点的循环链表基本操作
- 带头结点的单链表的基本操作
- C语言实现双向非循环链表(带头结点尾结点)的基本操作
- 线性表 - 带头结点的循环单链表
- 线性表(带头结点的单链表)
- 带头结点的线性链表的实现
- 带头结点的线性链表的编写与实现
- 线性表和带头结点的双向循环链表
- 线性链表的是否带头结点问题
- 带头结点的链表的基本操作(C语言)
- 《数据结构》进行曲--带头结点的单循环链表的基本操作
- 带头结点的链表
- 带头结点的链表
- Meta标签
- Hadoop 2.5.0-cdh5.3.6集群搭建
- 求素数筛法总结
- 使用spring @Scheduled注解执行定时任务、
- 使用Genymotion模拟器无法输入中文的解决办法
- 带头结点的线性链表的基本操作
- PKI学习-CA简介
- Android 自定义控件大道(一) 自定义属性
- 关于摩尔定律在当今社会的简单讨论
- STM32库函数开发工程模板
- 使用Activity.isfinishing()解决java.lang.IllegalArgumentException: View not attached to window manager
- nginx搭建https服务器
- SIP路由机制解析
- spring cloud 微服务之 Eureka与Feign