二、链表:
初识
链表基础知识:
什么是链表:
链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。
链表的入口节点称为链表的头结点也就是head。
链表的类型:
单链表
双链表
单链表中的指针域只能指向节点的下一个节点。
双链表:每一个节点有两个指针域,一个指向下一个节点,一个指向上一个节点。
双链表 既可以向前查询也可以向后查询。
如图所示:
循环链表
循环链表,顾名思义,就是链表首尾相连。
循环链表可以用来解决约瑟夫环问题。
链表的定义
链表的操作
链表性能分析
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<malloc.h>
#define ElemType int
typedef struct ListNode
{
ElemType data;
struct ListNode* next;//指针域(自身类型的指针类型),保存的地址是节点的地址,所以我们给出的是自身节点类型的next指针,即1节点指向的地址是2节点,节点1和2的类型是完全一样的
}ListNode;
typedef ListNode* List;
void InitList(List* head)//head相当于是 ListNode **head(二级指针,因为List本身就是节点的指针类型)
{
//(*head) = NULL;//一旦初始化完成,就是一个空链表,内部都是随机信息
//增加头节点(*head)就不能为空了,而是要指向头节点
(*head) = (ListNode *)malloc(sizeof(ListNode));
//申请空间之后,一定要进行断言
assert(*head != NULL);
(*head)->next = NULL;
}
//有多种形式去创建链表
//[1].尾插法创建
void CreateList_back(List* head)
{
*head = (ListNode*)malloc(sizeof(ListNode));
//断言,判断头节点创建是否成功
assert(*head != NULL);
(*head)->data = 1;//由于指向->的优先级高于星号 * ,因此要使用()
(*head)->next = NULL;
ListNode* p = *head;
for (int i = 2; i <= 10; ++i)//头节点1已经创建过,因此从2开始
{
ListNode* s = (ListNode*)malloc(sizeof(ListNode));
//断言,判断节点创建是否成功
assert(s != NULL);
s->data = i;
s->next = NULL;
p->next = s;//p的next指针指向s
p = s;//p指向下一个节点s
//1-->2-->3-->4-->5-->6-->7-->8-->9-->10-->Nul.
}
}
//[2].头插法创建
//顺序表只需要下标就能找到元素,而对链表来说,要想找到下边的数据,就只能靠next指针
//如果指针指向1而不是3,要想从1找到2和3,是不可能的,要从3找到1,是可以的,但是也需要通过2,然后2通过next找到1
//因此,在头插中,一定要对头指针保护好
//每做一个节点的插入,head就要更改,以保证head永远指向的整个链表的第一个节点
/*
void CreateList_front(List* head)
{
*head = (ListNode*)malloc(sizeof(ListNode));
assert(&head != NULL);
(*head)->data = 1;
(*head)->next = NULL;
for (int i = 2; i <= 10; ++i)//头节点1已经创建过,因此从2开始
{
ListNode* s = (ListNode*)malloc(sizeof(ListNode));
//断言,判断节点创建是否成功
assert(s != NULL);
s->data = i;
s->next = *head;
*head = s;//此时head不再指向1
//10-->9-->8-->7-->6-->5-->4-->3-->2-->1-->Nul.
}
}
*/
//头节点:在链表之前会增加一个多余的节点,这个节点有一个特性,它不保存数据,但它是提领整个链表的开始
//最主要的作用是,会使某些算法做一个统一的处理。
//现在结构是没有头节点的结构
//[3].使用头插法创建带头节点的链表
//此时head永远指向第一个节点
//链表想要安全性高,全靠头节点
/*
void CreateList_front(List* head)
{
for (int i = 1; i <= 10; ++i)//头节点是head,因此从1开始
{
ListNode* s = (ListNode*)malloc(sizeof(ListNode));
//断言,判断节点创建是否成功
assert(s != NULL);
s->data = i;
s->next = (*head)->next;//使用的是头插法,那么s->next,永远在头节点和第一个节点之间插入
(*head)->next = s;//此时head不再指向1
//10-->9-->8-->7-->6-->5-->4-->3-->2-->1-->Nul.
}
}
*/
//[4].使用头插法创建带头节点的链表
void CreateList_front(List* head)
{
ListNode* p = *head;
for (int i = 1; i <= 10; ++i)//头节点是head,因此从1开始
{
p = p->next = (ListNode*)malloc(sizeof(ListNode));
//断言,判断节点创建是否成功
assert(p != NULL);
p->data = i;
p->next = NULL;//使用的是头插法,那么s->next,永远在头节点和第一个节点之间插入
//1-->2-->3-->4-->5-->6-->7-->8-->9-->10-->Nul.
}
}
//显示链表
void ShowList(List head)
{
//ListNode* p = head;//不带头节点的显示
ListNode *p = head->next;//带头节点,却不显示头节点的显示
while (p != NULL)
{
printf("%d-->", p->data);
p = p->next;
}
printf("Nul.\n");//在链表尾给一个空的信息,以标识链表已经结束
}
//单链表的操作不难,但它的诀窍只有一点:
//必须要能分清楚,一个指针指向节点时,它的next是谁、data是谁,next->next是谁,或者这些指针如何来表示,此时对链表的操作就成功一大半了
void main()
{
List mylist;//定义了一个单链表,未初始化,内部信息都是随机值,无可用信息;
//InitList(mylist);为什么不能用mylist值传递?因为如果使用值传递,那么一旦出了InitList函数,mylist仍然还是随机值;值传递的内部的改变不会影响到外部的实参
//InitList(mylist);传递的mylist,虽然mylist是ListNode的指针,但是相对于List,就是值;
//只有对mylist取地址 &mylist才相对于List是地址形式传递
InitList(&mylist);//想要内部的改变希望影响到外部的实参,因此用地址传递
//CreateList_back(&mylist);
//ShowList(mylist);//因为不需要对其更改,就直接传mylist一级指针传递就行
CreateList_front(&mylist);
ShowList(mylist);//因为是尾插,所以是倒序,
system("pause");
}
单链表
Slist.h
#ifndef __SLIST_H__
#define __SLIST_H__
#include<stdio.h>
#include<malloc.h>
#include<assert.h>
/*
对于单链表来说,我们首先得有节点的类型 typedef struct Node
其次,我们增加了链表的管理方式,first、last、size。因此,我们需要定义相对应的List结构
这个结构有fist指针(指向头节点)、last指针(指向尾节点)、size(记录链表中有效节点的个数),任何的操作都要满足这种情况。
*/
#define ElemType int
typedef struct Node//这个是节点
{
ElemType data;
struct Node* next;
}Node, *PNode;//不光定义了节点的类型,还定义了节点指针的类型是 *PNode
typedef struct List//这个就是单链表
{
PNode first;
PNode last;
size_t size;
}List;
void InitList(List* list);
void push_back(List* list, ElemType x);
void show_list(List* list);
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 x);
int length(List* mylist);
void delete_val(List* list, ElemType key);
void sort(List *list);
void reverse(List *list);
void clear(List *list);
void destory(List *list);
//////////////////////////////////////////////////////////////////优化代码(C++思想):
Node * _buynode(ElemType x);
//对于头插和尾插的优化:
typedef Node* It;//迭代器
It begin(List *list);//返回头节点
It end(List *list);//返回最后一个节点的下一个节点
void insert(List *list, It pos, ElemType x);//在链表的某个位置插入
#endif //__SLIST_H__
Slist.cpp
#include"SList.h"
//初始化单链表
void InitList(List* list)
{
list->first = list->last = (Node*)malloc(sizeof(Node));//初始化,使first、last都指向同一个头节点
assert(list->first != NULL);
list->first->next = NULL;//如果first->next不初始化为空,那么在进行头插时,直接让 s->next = list->first->next; 会导致s->next->data是一个随机值
list->size = 0;
}
void push_back(List *list, ElemType x)
{
insert(list, end(list), x);//C++中更简单,只需要insert(begin(),x);就可以了
}
void push_front(List *list, ElemType x)
{
insert(list, begin(list), x);
}
/*
//[1]尾插法插入数据到链表
void push_back(List* list, ElemType x)
{
//所有的代码,一旦谈到开辟空间,就可以这样子做:
//Node *s = _buynode(x);
//
//Node* s = (Node*)malloc(sizeof(Node));//首先申请到所要插入的节点空间
//assert(s != NULL);
//s->data = x;
//s->next = NULL;
list->last->next = s;//这里的last原本指向的是头节点,此时要让last->next指向s使 新节点s 链接到链表上(last代表的是添加新节点前的最后一个节点)
list->last = s;//新节点s连接到链表上之后,last原本指向的节点就不再是最后一个节点了,因此要让last指向s,以保证last永远指向最后一个节点
list->size++;
}
//[2]头插法插入节点
//头节点的作用是提领整个链表,头插法是在第一个节点和头节点之间插入,而并非在头节点之前插入,例如: head->1->2->3,就是在head 和 1之间插入
//在头插的时候,一定要注意,所插入的是否为第一个节点;
//而尾插就不需要,永远都要更改last
void push_front(List* list, ElemType x)
{
Node *s = _buynode(x);
//Node* s = (Node*)malloc(sizeof(Node));
//assert(s != NULL);
//s->data = x;
//s->next = NULL;
s->next = list->first->next;
list->first->next = s;
if (list->size == 0)
{
list->last = s;//让last指针指向新创建的节点s
}
list->size++;
//考虑last指针
//如果现在的链表已经有数据了,那么头插入不会造成错误;
//如果是刚开始的时候,链表一个数据都没有,此时last指针和first指针都指向头节点
//此时,当头插插入节点 s 时,s->next指向空,first->next指向 s,就链接起来了
//但是,我们忽略了一个问题,如果头插法插入的节点是第一个节点的时候,就要考虑我们要修改last指针,让其指向我们所插入的第一个节点
//如果我们修改完成,其它的节点在头插的时候,就没有任何问题了,因为其它节点进行头插入,而1将永远是最后一个节点
//但是,如果头插完再进行尾插呢?
}
*/
//[3]遍历显示链表
void show_list(List* list)
{
Node* p = list->first->next;//要想访问一个链表,就要让它指向first的第一个有效节点
while (p != NULL)
{
printf("%d-->", p->data);
p = p->next;
}
printf("Nul.\n");
}
//[4]尾删
//要读考虑边界条件,如果链表为空,就不需要删除了
//尾删就是找到last然后free(list->last)就可以了,关键是last指向的是最后一个节点,要先让last指向倒数第二个节点,才能删除最后一个节点
void pop_back(List* list)
{
if (list->size == 0)
return;
Node* p = list->first;//定义p指针指向头节点
while (p->next != list->last)//先遍历单链表,使其能够指向倒数第二个节点; 当while循环结束的时候,p就指向了倒数第二个节点
p = p->next;
free(list->last);//
list->last = p;//修改last的指向,使其指向删除后的最后一个节点,之前的指向就没了,即被修改;
list->last->next = NULL;//为最后一个节点赋空
list->size--;
}
//[5]头删
//相当于删除第一个节点
//应充分考虑如下情况:
//当链表里只有一个节点时,就会发现,此时头删法只能删除最后一个节点(也是第一个节点)
//此时last的指向就变成了一个随机值;因此要更改指向,使其指向head
//因此,头删法删除时需要考虑删除的是否是最后一个节点
//如果是,就要更改last指向
void pop_front(List* list)
{
if (list->size == 0)
return;
Node* p = list->first->next;//指向第一个节点
list->first->next = p->next;//p就被从链表上摘下来了
free(p);//释放掉该节点
if (list->size == 1)//说明删除的是最后一个节点
list->last = list->first;
list->size--;
}
//[6]插入数据
//这个方法有一个前提:
//既然要按照值插入,那么这个值插入的关系是:假如是按照升序的顺序插入
//这就要求,在插入之前的数据,一定要是一个有序的数据
//同时还要考虑到着一种情况:要插入的位置,是整个链表的末尾(即,要插入的值,比链表里的值都大)
//此时,直接
void insert_val(List* list, ElemType x)
{
Node *s = _buynode(x);
//Node* s = (Node*)malloc(sizeof(Node));
//assert(s != NULL);
//s->data = x;
//s->next = NULL;
Node* p = list->first;
while (p->next != NULL && p->next->data < x)
p = p->next;
//
if (p->next == NULL)//说明p已经找到了最后一个节点
list->last = s;//更改last指向
s->next = p->next;
p->next = s;
list->size++;
}
//[7]查找数据
//命名习惯:
//插入值,用x;
//获取值,用v;
//查找,用key;
Node* find(List* list, ElemType key)
{
Node* p = list->first->next;
while (p != NULL && p->data != key)//这个循环条件就会把找到和没找到,都包含进去;没有找到p = NULL返回,如果找到了,p指向当前的节点,就会把当前节点的地址返回
p->next;
return p;
//注意:不能这样写:while(p->data != key && p!=NULL)
//while(p->data != key && p!=NULL) 和 while (p != NULL && p->data != key)天壤之别
//如果真找不到数据,p=NULL,就没有p->data这个指向关系了,但是由于 p->data != key 条件在前面,就会导致程序崩溃
//换句话说,p能够指向->data求值的前提条件是:p != NULL
}
//[8]求长度
int length(List* list)
{
return list->size;
}
//[9]根据data删除节点
void delete_val(List* list, ElemType key)
{
if (list->size == 0)
return;
Node* p = find(list, key);
if (p == NULL)
{
printf("要删除的数据不存在.\n");
return;
}
else
{
//p指针从第一个节点开始,p移动,最后指向当前要删除的节点;
//比如要删除的是 2,那么要做的工作就是把 2 的前驱节点1的后继指向 2 本身的后继3
//但问题在于,2的前驱节点1的指针不太好寻找,除非再遍历一遍找出(p->next = key的p就是该节点),但是太麻烦了没有必要
//可以直接删除掉
//1.直接对2进行删除:直接把3的值拷贝一份,覆盖掉2节点的数据,此时原本要删除节点2,就变成了要删除节点3(p->next = q->next)
//2.还有一个方法就是,不使用find()查找,而是另外写一个查找方法,该方法返回当前节点的前一个节点的地址
//此时代码还有一个错误,即如果所要删除的是最后一个节点,last指针
//那么倒数第二个节点删除是否要考虑进来呢?
if (p == list->last)
{
pop_back(list);//在pop_back中删除完就会list->size--;
}
else
{
Node* q = p->next;
p->data = q->data;
p->next = q->next;
free(q);//释放q指针指向的节点
list->size--;
}
}
}
//[10]排序
//这里不是对数据进行交换,而是真正的交换节点
void sort(List *list)
{
if (list->size == 0 || list->size == 1)//0是空链表,1是只有一个结点的链表,都不用排
return;
//在非0和1个节点的情况加,就将当前链表分割为2个
Node* s = list->first->next;//先让指针s,指向链表的第一个节点
Node* q = s->next;
//1.把链表断开
list->last = s;//last指向s节点,last就不再指向原先的最后一个节点了
list->last->next = NULL;//last->NULL,那么后面的节点就从这个链表断开了
//2.把q链表中的每一个节点摘下来放到原先链表,并按照值大小顺序插入
while (q != NULL)//q只要不为空,就说明q之后,还有链表节点的存在
{
s = q;
q = q->next;
//这就相当于是,指针s,指向现在新有的链表,然后q = q->next
//那么s所值的结点,就可以断下来进行插入
//3.把节点摘下
Node *p = list->first;//构造指针p,指向头节点
while (p->next != NULL && p->next->data < s->data)
p = p->next;
//p->next != NULL,p->next->data < s->data,此时p = p->next,从头节点移动,指向了第一个节点
//此时p->next == NULL,因此,此时就相当于在尾部插入数据
//更改last指向,
if (p->next == NULL)
{
list->last = s;
}
//完成插入操作
s->next = p->next;
p->next = s;
}//1-->2-->3-->4-->5-->6-->7-->8-->9-->Nul.
}
//[11]单链表逆置
//这里不同于顺序表的首位两两交换,一般来说,牵扯到链表的顺序表,我们很少对链表的节点数据进行交换、操作
//一般逆置、排序都是针对节点整体进行操作
void reverse(List *list)
{
if (list->size == 0 || list->size == 1)
return;
Node *p = list->first->next;//创建指针s,指向第一个节点
Node *q = p->next;//创建指针q指向将要断开的节点的头节点
//断开链表
list->last = p;
list->last->next = NULL;
//遍历断开后的链表,使得指针q指向最后一个节点
while (q != NULL)
{
p = q;
q = q->next;
p->next = list->first->next;
list->first->next = p;
}
}
//[12]清除链表
//清除和销毁的区别是:
//清除:释放掉有效节点,保留头节点
//销毁:清除有效节点 和 头节点
void clear(List *list)
{
if (list->size == 0)
return;
Node *p = list->first->next;
while (p != NULL)
{
list->first->next = p->next;
free(p);
p = list->first->next;
}
list->last = list->first;
list->size = 0;
}
//[13]销毁链表
//销毁:清除有效节点 和 头节点
void destory(List *list)
{
clear(list);
free(list->first);
list->first = list->last = NULL;//预防野指针
}
//////////////////////////////////////////////////////////////////优化代码(C++思想):
Node * _buynode(ElemType x)
{
Node *s = (Node *)malloc(sizeof(Node));
assert(s != NULL);
s->data = x;
s->next = NULL;
return s;
}
It begin(List *list)
{
return list->first->next;//返回第一个节点
}
It end(List *list)
{
return list->last->next;//返回最后一个节点的下一个节点,单链表中为NULL
}
void insert(List *list, It pos, ElemType x)
{
//原先的位置指的是是0、1、2下标
//而现在的位置,指的是单链表中某个节点的地址,现在往往是根据该节点的前驱进行插入
//所谓的pos,指的是某个节点的地址;如果指明要在2的位置插入,其实说明的是,要在2的前面进行插入,而非后面
Node *p = list->first;
while (p->next != pos)
{
p = p->next;
}
Node *s = _buynode(x);//先把节点购买出来
//购买完成,进行连接
s->next = p->next;
p->next = s;
if (pos == NULL)//现在插入的位置是在最后一个结点之后插入,那么一定要注意更改last的指向
list->last = s;
list->size++;
}
pop_front
insert_val
自己画图动手试试
代码不是背出来的,一定是编写出来的
要掌握这种排序思想:
这里的是:把整个单链表断开,然后从剩下的链表当中每次取一个节点进来,按照有效的顺序进行插入,
最终形成一个所需要的,升序或者降序的排序!
自己画图动手试试
逆置:
自己画图动手试试
清除:
Main.cpp
#define _CRT_SECURE_NO_WARNINGS //这个要放到最上面
#include"SList.h"
void main()
{
List mylist;
InitList(&mylist);
ElemType item;
Node* p = NULL;
int select = 1;
while (select)
{
printf("**************************************************\n");
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] destory [0] quit_system *\n");//destory这个方法不应该由客户来调动;而是应该在退出程序时摧毁
printf("**************************************************\n");
printf("请选择:>");
scanf("%d", &select);
if (select == 0)
break;
switch (select)
{
case 1:
printf("请输入要尾插的数据(-1结束):>");
while (scanf("%d", &item), item != -1)
{
push_back(&mylist, item);
}
break;
case 2:
printf("请输入要进行头插的数据(-1结束):>");
while (scanf("%d", &item), item != -1)
{
push_front(&mylist, item);
}
break;
case 3:
show_list(&mylist);
break;
case 4:
pop_back(&mylist);
break;
case 5:
pop_front(&mylist);
break;
case 6:
printf("请输入要插入的数据:>");
scanf("%d", &item);
insert_val(&mylist, item);
break;
case 7:
printf("请输入要查找的数据:>");
scanf("%d", &item);
p = find(&mylist, item);//如果找到了,就把该节点的地址返回,找不到就返回NULL;
if (p == NULL)
{
printf("要查找的数据在链表中,不存在\n");
}
break;
case 8:
printf("单链表的长度为:%d \n", length(&mylist));
break;
case 9:
printf("请输入要删除的值:>");
scanf("%d", &item);
delete_val(&mylist, item);
break;
case 10:
sort(&mylist);
break;
case 11:
reverse(&mylist);
break;
case 12:
clear(&mylist);
break;
//case 13:
//destory(&mylist);//摧毁不应该由用户在菜单里调用,而是应该在退出系统时调用
//break;
default:
printf("输入的选择错误,请重新输入");
break;
}
}
destory(&mylist); //在退出程序时调用
}
静态链表
StaticList.cpp
StaticList.h
Main.cpp
单循环链表
SCList.h
#ifndef __SCLIST_H__
#define __SCLIST_H__
#include<stdio.h>
#include<malloc.h>
#include<assert.h>
#define ElemType int
typedef struct Node
{
ElemType data;
struct Node* next;
}Node, *PNode;//定义好节点和节点的指针
typedef struct List
{
PNode first;
PNode last;
int size;
}List;
Node* _buynode(ElemType x);
void InitSCList(List* list);
void push_back(List* list, ElemType x);
void show_list(List* list);
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 x);
int length(List* mylist);
void delete_val(List* list, ElemType key);
void sort(List* list);
void reverse(List* list);
void clear(List* list);
void destory(List* list);
#endif //__SCLIST_H__
SCList.cpp
#include"SCList.h"
Node* _buynode(ElemType x)
{
Node* s = (Node*)malloc(sizeof(Node));
assert(s != NULL);
s->data = x;
s->next = NULL;
return s;
}
void InitSCList(List* list)
{
Node* s = (Node*)malloc(sizeof(Node));
assert(s != NULL);
list->first = list->last = s;//由于现在没有真实节点,链表只有一个头结点,first、last都指向头节点,然后list->next 指向NULL
//此时,由于是循环链表,要让最后一个节点的next域指向头节点,
//因此不能让其指向空,因为就算只有一个头节点,也要保持循环特性,
//因此要让list->next也指向头节点
list->last->next = list->first;
list->size = 0;
}
/*
插入节点时,要考虑插入的是不是第一个节点
删除节点时,要考虑删除的是不是最后一个节点
检查插入程序:
头插、尾插、中间插入各试一次,没有问题基本OK
*/
void push_back(List* list, ElemType x)
{
//1.先创建出要插入的节点
Node* s = _buynode(x);
//2.把节点在尾部进行连接
list->last->next = s;//不能list->last = s; 要永远保证最后一个节点的next指向新的s节点
//3.修改last的指向
list->last = s;
//4.让最后一个节点的next域指向头节点
list->last->next = list->first;
//size++
list->size++;
}
void push_front(List* list, ElemType x)
{
//1.先创建要插入的节点
Node* p = _buynode(x);
//2.插入
p->next = list->first->next;
list->first->next = p;
//3.需要考虑到插入前链表为空的情况,链表为空时,一定要更改last的指向,否则就不能保证链表结构的正确性
if (list->first == list->last)
{
list->last = p;
list->last->next = list->first;
}
list->size++;
}
void show_list(List* list)
{
Node* p = list->first->next;
while (p != list->first)//转了一圈之后,只要不为头,就说明整个链表还没结束 //如果访问到最后一个,p->next指向的是头节点,说明访问完了
{
printf("%d-->", p->data);
p = p->next;
}
printf("Nul.\n");//表示链表结束
}
void pop_back(List* list)
{
if (list->size == 0)
return;
Node* p = list->first;
while (p->next != list->last)
{
p = p->next;
}
free(list->last);
list->last = p;
list->last->next = list->first;//这行代码到底有没有必要存在呢?
list->size--;
}
void pop_front(List* list)
{
if (list->size == 0)
return;
Node* p = list->first->next;
list->first->next = p->next;
free(p);
if (list->size == 1)//说明现在删除的是最后一个节点,要让改变last的指向
{
list->last = list->first;
}
//list->last = p->next;//不对,这么写,那么last永远指向的都是第二个节点
list->size--;
}
//[6]插入
//按值插入说明目前的链表已经有序了
void insert_val(List* list, ElemType x)
{
Node* p = list->first;//先申请一个指针,指向头节点
//要先判断,是否到了最后一个节点
//p->next == list->last 说明要插入的数据在尾部
while (p->next != list->last && p->next->data < x)//开始循环遍历
{
p = p->next;
}
if (p->next == list->last && p->next->data < x)//其实这p->next->data < x已经不用再判断了,前面while循环已经把符合这个条件的筛出来了
{
//说明需要尾部插入
push_back(list, x);
}
else
{
Node* s = _buynode(x);
s->next = p->next;
p->next = s;
list->size++;
}
}
//[7]查找
Node* find(List* list, ElemType key)
{
if (list->first == list->last)
return NULL;
Node* p = list->first->next;
while (p != list->first && p->data != key)
{
p = p->next;
}
if (p == list->first)//找了一圈之后,p == list->first,就说明没有找到
return NULL;
return p;
}
//[8]长度
int length(List* list)
{
return list->size;//只要结构控制得好,很多信息非常得出来
}
//[9]按值删除
void delete_val(List* list, ElemType key)
{
if (list->size == 0)
return;
Node* p = find(list, key);//查找成功返回的是该数值节点得地址,失败返回NULL
if (p == NULL)
{
printf("要删除得数据不存在.\n");
return;
}
if (p == list->last)
{
pop_back(list);
}
else
{
//两种方式删除:
//1.循环遍历找到该节点的前驱,然后删除
//2.将待删除节点的后继的data域复制到删除节点的data域,覆盖掉它,然后删除该节点就转化成了删除该节点的后继节点
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->first->next;
Node* q = s->next;//保留断开后链表的头节点
list->last->next = NULL;//list->last->next原本是指向list->first的,这么做是让其先不再循环
list->last = s;//
list->last->next = list->first;//即p.next不再指向第二个节点,而是指向头节点,使让其再次成循环
//接下来就是按值插入
while (q != NULL)//由于之前把list->last->next = NULL,因此q跟NULL的比较就是标志;这就是给list->last->next赋值为空的原因,否则其还指向头,就还得用头节点来判断
{
s = q;//s指向q
q = q->next;
Node* p = list->first;
while (p->next != list->last && p->next->data < s->data)
{
p = p->next;
}
if (p->next == list->last && p->next->data < s->data)
{
s->next = list->last->next;
list->last->next = s;
list->last = s;
}
else
{
s->next = p->next;
p->next = s;
list->size++;
}
}
}
/*
命名习惯:
申请节点:Node *s,以s来命名
遍历链表、或者充当临时变量的指针:Node *p
删除一把用:Node *q,以q来命名
变量不够了:Node *t,以t来补充
*/
void reverse(List *list)
{
if (list == 0 || list->size == 1)
return;
Node *p = list->first->next;
Node *q = p->next;
//先断开链表
list->last->next = NULL;
list->last = p;
list->last->next = list->first;//尾节点的next指向头节点,就是构成循环,清晰明了!
//为啥不用p->next = list->first?//用这个的话,还要先分析出p是最后一个节点
//这两种方式功能上一样,但是在语义上不一样;上一个一目了然,而下面这个需要通过分析才能知道它是干啥的
while (q != NULL)
{
p = q;
q = q->next;
p->next = list->first->next;
list->first->next = p;
}
}
void clear(List *list)
{
Node *p = list->first->next;
while (p != list->first)//p != list->first 时,说明没有释放完成
{
list->first->next = p->next;
free(p);
p = list->first->next;
}
list->last = list->first;
list->last->next = list->first;
list->size = 0;
}
void destory(List *list)
{
clear(list);
free(list->first);
list->first = list->last = NULL;
}
Main.cpp
#define _CRT_SECURE_NO_WARNINGS //这个要放到最上面
#include"SCList.h"
void main()
{
List mylist;
InitSCList(&mylist);
ElemType item;
Node* p = NULL;
int select = 1;
while (select)
{
printf("**************************************************\n");
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] destory [0] quit_system *\n");//destory这个方法不应该由客户来调动;而是应该在退出程序时摧毁
printf("**************************************************\n");
printf("请选择:>");
scanf("%d", &select);
if (select == 0)
break;
switch (select)
{
case 1:
printf("请输入要尾插的数据(-1结束):>");
while (scanf("%d", &item), item != -1)
{
push_back(&mylist, item);
}
break;
case 2:
printf("请输入要进行头插的数据(-1结束):>");
while (scanf("%d", &item), item != -1)
{
push_front(&mylist, item);
}
break;
case 3:
show_list(&mylist);
break;
case 4:
pop_back(&mylist);
break;
case 5:
pop_front(&mylist);
break;
case 6:
printf("请输入要插入的数据:>");
scanf("%d", &item);
insert_val(&mylist, item);
break;
case 7:
printf("请输入要查找的数据:>");
scanf("%d", &item);
p = find(&mylist, item);//如果找到了,就把该节点的地址返回,找不到就返回NULL;
if (p == NULL)
{
printf("要查找的数据在链表中,不存在\n");
}
break;
case 8:
printf("单链表的长度为:%d \n", length(&mylist));
break;
case 9:
printf("请输入要删除的值:>");
scanf("%d", &item);
delete_val(&mylist, item);
break;
case 10:
sort(&mylist);
break;
case 11:
reverse(&mylist);
break;
case 12:
clear(&mylist);
break;
//case 13:
// destory(&mylist);//摧毁不应该由用户在菜单里调用,而是应该在退出系统时调用
// break;
default:
printf("输入的选择错误,请重新输入");
break;
}
}
//destory(&mylist); //在退出程序时调用
}
双向链表
DList.h
#ifndef __DLIST_H__
#define __DLIST_H__
#include<stdio.h>
#include<malloc.h>
#include<assert.h>
#define ElemType int //定义出元素的类型
typedef struct Node
{
ElemType data;
struct Node *pre;//前驱结点
struct Node *next;//后继节点
}Node,*PNode;
//对双向链表同样采用单向链表的管理模式
//定义出list结构,让last指向最后一个节点,first指向头节点,size记录真实节点的个数
typedef struct List
{
PNode first;
PNode last;
int size;
}List;
Node* _buynode(ElemType x);
void InitSCList(List* list);
void push_back(List* list, ElemType x);
void show_list(List* list);
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 x);
int length(List* mylist);
void delete_val(List* list, ElemType key);
void sort(List* list);
void reverse(List* list);
void clear(List* list);
void destory(List* list);
void InitDList(List *list);
#endif
DList.cpp
#include"DList.h"
Node* _buynode(ElemType x)
{
Node* s = (Node*)malloc(sizeof(Node));
assert(s != NULL);
s->data = x;
s->pre = s->next = NULL;
return s;
}
//由于链表是带有头节点的,因此创建头节点的任务在链表初始化时完成
void InitDList(List* list)
{
Node* s = (Node*)malloc(sizeof(Node));
assert(s != NULL);//断言成功才代表此头节点创建成功
list->first = list->last = s;
list->first->pre = list->last;
list->last->next = list->first;
list->size = 0;
}
void push_back(List* list, ElemType x)
{
Node* s = _buynode(x);
s->next = list->last->next;
s->next->pre = s;
s->pre = list->last;
list->last->next = s;
list->last = s;
list->size++;
}
//在只有一个头结点的时候进行头插,必须要考虑到list->last的情况
//list->first->next 为NULL,NULL是没有前驱pre的,因此代码在运行时就会崩溃!
//因此,要考虑链表中之后一个头结点(无有效节点)的情况
void push_front(List* list, ElemType x)
{
Node* s = _buynode(x);
s->next = list->first->next;
s->next->pre = s;
s->pre = list->first;
list->first->next = s;
if (list->first == list->last)
list->last = s;
list->size++;
}
void show_list(List* list)
{
Node* p = list->first->next;
while (p != list->first)
{
printf("%d-->", p->data);
p = p->next;
}
printf("Nul.\n");
}
void pop_back(List* list)
{
if (list->size == 0)
return;
Node* p = list->last;
list->last = list->last->pre;
p->next->pre = p->pre;
p->pre->next = p->next;
free(p);
list->size--;
}
//要考虑到头删最后一个节点的情况
//因为如果时最后一个节点,诸侯一个节点的后继为NULL,是没有前驱的
void pop_front(List* list)
{
if (list->size == 0)
return;
Node* p = list->first->next;
p->next->pre = p->pre;
p->pre->next = p->next;
if (list->size == 1)
list->last = list->first;
free(p);
list->size--;
}
//按值插入的前提是链表是有序的(这里是升序),按照升序的方式插入
void insert_val(List* list, ElemType x)
{
Node* p = list->first;
while (p->next != list->last && p->next->data < x)
p = p->next;
if (p->next == list->last && p->next->data < x)
{
push_back(list, x);
}
else//这种情况是,找到了待插入的位置,且这个位置不是最后,此时p指针指向的是待插入位置的前一个结点
{
Node* s = _buynode(x);
s->next = p->next;
p->next->pre = s;
s->pre = p;
p->next = s;
list->size++;
}
}
Node* find(List* list, ElemType key)
{
Node* p = list->first->next;
while (p != list->first && p->data != key)//p == NULL退出循环,说明没有找到,p->data == key退出循环说明找到了
p = p->next;
if (p == list->first)
return NULL;
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("要删除的数据是不存在的.\n");
return;
}
//删除节点只需要考虑两点:
//被删除的是普通节点,还是尾节点
if (p == list->last)//尾节点
{
pop_back(list);
}
else//普通节点
{
//修改其中的指针就行了
p->pre->next = p->next;
p->next->pre = p->pre;
free(p);
list->size--;
}
}
//排序就是先把链表从第一个节点处断开,
//然后从剩下的链表中提取节点,按着值插入的方式插入到原链表中即可
void sort(List* list)
{
if (list->size == 0 || list->size == 1)
return;
Node* s = list->first->next;
Node* q = s->next;
//断开
list->last->next = NULL;
list->last = s;
//循环
list->last->next = list->first;
list->first->pre = list->last;
while (q != NULL)
{
s = q;
q = q->next;
Node* p = list->first;
//先将指针移动到尾节点的前一个位置
while (p->next != list->last && p->next->data < s->data)
p = p->next;
if (p->next == list->last && p->next->data < s->data)
{
//进行尾插
s->next = list->last->next;//s->next就是最后节点的next
s->next->pre = s;//p->next的前驱,即相当于是list->last的前驱
s->pre = list->last;
list->last->next = s;
list->last = s;
}
else
{
s->next = p->next;
s->next->pre = s;
s->pre = p;
p->next = s;
}
}
}
void reverse(List* list)
{
if (list->size == 0 || list->size == 1)
return;
Node* p = list->first->next;
Node* q = p->next;
//断开
list->last->next = NULL;
list->last = p;
//循环
list->last->next = list->first;
list->first->pre = list->last;
while (q != NULL)
{
p = q;
q = q->next;
p->next = list->first->next;
p->next->pre = p;
p->pre = list->first;
list->first->next = p;
}
}
void clear(List* list)
{
if (list->size == 0)
return;
Node* p = list->first->next;
while (p != list->first)
{
p->next->pre = list->first;
list->first->next = p->next;
free(p);
p = list->first->next;
}
list->last = list->first;
list->size = 0;
}
void destory(List* list)
{
clear(list);
free(list->first);
list->first = list->last = NULL;
}
Main.cpp
#define _CRT_SECURE_NO_WARNINGS
#include"DList.h"
void main()
{
List mylist;
InitDList(&mylist);
ElemType item;
Node* p = NULL;
int select = 1;
while (select)
{
printf("**************************************************\n");
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] destory [0] quit_system *\n");//destory这个方法不应该由客户来调动;而是应该在退出程序时摧毁
printf("**************************************************\n");
printf("请选择:>");
scanf("%d", &select);
if (select == 0)
break;
switch (select)
{
case 1:
printf("请输入要尾插的数据(-1结束):>");
while (scanf("%d", &item), item != -1)
{
push_back(&mylist, item);
}
break;
case 2:
printf("请输入要进行头插的数据(-1结束):>");
while (scanf("%d", &item), item != -1)
{
push_front(&mylist, item);
}
break;
case 3:
show_list(&mylist);
break;
case 4:
pop_back(&mylist);
break;
case 5:
pop_front(&mylist);
break;
case 6:
printf("请输入要插入的数据:>");
scanf("%d", &item);
insert_val(&mylist, item);
break;
case 7:
printf("请输入要查找的数据:>");
scanf("%d", &item);
p = find(&mylist, item);//如果找到了,就把该节点的地址返回,找不到就返回NULL;
if (p == NULL)
{
printf("要查找的数据在链表中,不存在\n");
}
break;
case 8:
printf("单链表的长度为:%d \n", length(&mylist));
break;
case 9:
printf("请输入要删除的值:>");
scanf("%d", &item);
delete_val(&mylist, item);
break;
case 10:
sort(&mylist);
break;
case 11:
reverse(&mylist);
break;
case 12:
clear(&mylist);
break;
//case 13:
//destory(&mylist);//摧毁不应该由用户在菜单里调用,而是应该在退出系统时调用
//break;
default:
printf("输入的选择错误,请重新输入");
break;
}
}
destory(&mylist); //在退出程序时调用
}
标签:last,基础,list,next,链表,List,节点,first
From: https://www.cnblogs.com/Epiephany/p/17103832.html