数据结构(2.2)线性表之单链表的具体实现

前言

本篇主要讲的是在增加了管理结构的基础上对单链表进行的插入删除、查找排序等操作(同顺序表的操作一样)。由于链式存储的特性,在进行某些操作时可以特别处理,在后文中会说明。

链表的管理结构

一个链表由指向头结点的指针和指向最后一个结点的指针和链表的长度构成。这样,我们对链表进行操作时,还要考虑管理结构的变化。例如在尾部插入结点时,除了生成结点保存数据以外,还要修改last指针的指向以及链表的长度。

img1
链表的初始化和插入在上一篇中讲得很清楚了,即使增加了管理结构也是大同小异。时间有限,只在比较特殊的地方给出图解,其余部分在注释里说明。

实现的操作

  • 尾部插入
  • 头部插入
  • 展示单链表
  • 尾部删除
  • 头部删除
  • 按值插入(要求插入前的链表是有序的,代码中为顺序)
  • 按值查找
  • 获取链表长度
  • 按值删除
  • 排序
  • 逆置(即前后转换链表)
  • 清除单链表
  • 摧毁单链表

在进行头部插入与删除时last指针的指向问题

在头部插入第一个结点时,由于last指针还指向头结点,因此在除了插入结点外还需要额外让last指针重新指向新的结点;同理,在头部删除最后一个结点时,last指针指向的结点空间已经被释放掉了,因此还要额外让last指针重新指向头结点。在这两种情况下如果不进行特殊处理,会造成last指针指向错误的问题,虽然插入、删除操作是正确的,但是从管理结构的角度来看仍是有错误的。

img2

按值删除的几种方法

当我们进行按值删除操作时,首先用find(按值查找)的方法查找链表中是否有这个值,若有则会返回该结点的地址。但是我们删除时,需要释放该结点的内存空间,那么就要修改它前驱的next值,该怎么处理呢?

  • 寻常的方法一:遍历整个链表,找到该结点的前驱进行修改即可

img3

  • 巧妙的方法二:不直接删除该结点,而是将该结点后继的数据域复制过来,删除后继结点,再修改该结点的next值为后继结点的next

    img4

    用这种方法仍需要考虑last指针的指向问题,当要删除的结点是倒数第二个时,如上图,s结点的内存空间被释放后,last指针指向的是一个无意义的地址,因此需要重新设置。而当要删除的结点是最后一个时,只要当做尾部删除,调用push_back方法即可。

  • 另类的方法三:修改(或者另写一个)find方法,让它返回该结点前驱的地址,这样进行删除操作就很方便了

如何巧妙地进行逆置

对单链表进行逆置,如果想和顺序表中一样,开辟一块辅助空间来储存数据,再倒序存入原有顺序表中,这很明显是不可取的。因为在单链表中只能通过结点找到其后继,却不能返回去找前驱,所以没办法像顺序表一样通过下标直接访问到最后一位。当然,我们也可以用一个顺序表来储存链表中的数据,再倒序存入链表中,这无疑是很麻烦的。

万幸的是,由于链表的特性,可以有比较巧妙的方法来进行逆置:用一个指针指向首元结点的后继,从此处将链表断为两个部分,再从该指针开始取出后续结点,对链表进行头部插入即可。

img5

img6

img7

针对该链表的排序

当我们需要给链表进行排序时,可以参考顺序表的方法,例如冒泡排序、选择排序等。虽然对这些方法略有了解,但是没有进行过具体的整理,也不太清楚在链表中这些方法的性能与线性表中是否有差别。但是针对我们写的这个单链表,其实可以用个比较巧妙的思路,就不需要自己写排序的算法了。

  • 之前写的按值插入方法可以在链表本身有序(代码中为顺序)的前提下按值的大小进行插入,插入后的链表也是有序的。因此我们可以充分利用这个方法,类似上文逆置的思路,先将链表断开为两个部分,然后再从指针处不断取出后续结点,对结点进行按值插入即可。

几点收获

  1. 写代码时要有考虑边际条件的意识,减少低级逻辑错误
  2. 多考虑特殊情况(怎样发生的、后果是什么、怎么处理)
  3. 广泛打开思路,不要局限于原有的思维里,同时学会举一反三

全部代码

工程图

img8

SList.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;

//初始化单链表
void InitList(List *list);

//1.尾部插入
void push_back(List *list,ElemType x);
//2.头部插入
void push_fount(List *list,ElemType x);
//3.展示单链表
void show_list(List *list);
//void show_list1(List list);
//4.尾部删除
void pop_back(List *list);
//5.头部删除
void pop_fount(List *list);
//6.按值插入(要求插入前的链表是有序的(此处为顺序
void insert_val(List *list,ElemType x);
//7.按值查找
Node* find(List *list,ElemType x);
//Node* find1(List list,ElemType x);
//8.获取长度
int length(List *list);
//int length1(List list);
//9.按值删除
void  delete_val(List *list,ElemType x);
//10.排序
void sort(List *list);
//11.逆置(前后转换
void resver(List *list);
//12.清除单链表 ->只清除数据,保留头结点
void clearList(List *list);
//13.摧毁 ->包括头结点在内一起摧毁
void destroy(List *list);
SList.cpp文件存放单链表操作函数的具体实现

#include "SeqList.h"

//初始化单链表
void InitList(List *list){
   
    //创建头结点 链表的first和last都指向它
    list->first = list->last = (Node*)malloc(sizeof(Node));
    assert(list->first!=NULL);
    list->first->next 
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值