【笔记】数据结构(1)之链表

线性表

1.线性结构

线性结构是n(>=0)个节点的有穷序列
假定该序列由a(1),a(2),a(3)…a(n)组成,每一个元素代表线性结构的每一个节点,那么,我们称a(1)为起始节点,a(n)为终端节点,而他们的下标i为a(i)在线性表中的序号位置
现在我们取序列中不位于首端和尾端的三个元素:a(i-1),a(i),a(i+1),此时我们称a(i)为中间节点,a(i-1)和a(i+1)为前驱节点后继节点,a(i-1)是a(i)的直接前驱,a(i+1)是a(i)的直接后继
为了满足运算的封闭性,通常允许一种逻辑结构出现不含任何节点的情况,此时我们称这种结构为空结构
线性结构的基本特征:若至少含有一个节点,那么除起始节点没有直接前驱,其他节点都有且仅有一个直接前驱;除终端节点没有直接后继,其他节点都有且仅有一个直接后继

2.线性表

线性表是线性结构,它具有以下特征:

  1. 线性表是有序的,即线性表中的元素是有序
  2. 线性表是可索引的,即线性表中的元素可以通过索引访问
  3. 线性表是可插入的,即线性表可以插入元素
  4. 线性表是可删除的,即线性表可以删除元素
  5. 线性表是可复制的,即线性表可以复制
  6. 线性表是可连接的,即线性表可以连接
  7. 线性表是可枚举的,即线性表可以枚举
    其中,所含节点个数称为线性表的长度(简称表长),表长为0的线性表称为空表
    线性表的基本操作:
  8. 创建:创建一个线性表
  9. 销毁:销毁一个线性表
  10. 清空:清空一个线性表
  11. 表间赋值:将线性表a赋给线性表b
  12. 取值:返回线性表a中序号为i的元素
  13. 元素赋值:将线性表a中序号为i的元素赋值为x
  14. 查找:在线性表a中查找值为x的元素,并返回其序号i
  15. 插入:将元素x插入到线性表a中,插入位置为i
  16. 删除:删除线性表a中序号为i的元素,并返回该元素
  17. 长度:返回线性表a的长度
  18. 判空:判断线性表a是否为空
  19. 连接:将线性表b连接在a的末尾
  20. 枚举:遍历线性表a,对线性表a中的每个元素执行一次操作

3.顺序表的结构实现

顺序表是线性表的顺序存储结构,顺序表的一个节点存储线性表的一个节点内容(数据元素),并且不包含其他信息,所有储存节点与其相对应的元素间的逻辑关系是一对一的邻接关系,那么即知顺序表的特点:逻辑结构中相邻的节点在存储结构中仍相邻

//**用一维数组实现线性表**
//假定*datatype*为线性表的数据元素类型
const maxsize = 顺序表的容量        //定义一个常量用以存储顺序表的元素个数
typedef struct                     //定义顺序表结构 
{
    datatype data[maxsize];        //在结构体中定义一个数组用以存储数据表的元素
    int last;                      //定义一个变量last,记录顺序表中最后一个元素即终端元素的位置 
} sqlist;
sqlist L;
L.last = -1;                       ///初始化顺序表,-1表示顺序表为空 
L.data[L.last - 1];

//注意:last-1才是代表数据表中终端节点的位置,因为数组下标是从0开始

线性表的连接实现

1.单链表

单链表节点结构图示:

┌─────────┬───────┐
│  Data   │  Next │
├─────────┼───────┤
│   数据  │  指针 │
└─────────┴───────┘
    ▲        ▲
    │        │
 数据域    指针域

如图,单链表的一个存储节点包括俩个部分,分别是Data部分数据域和Next部分指针域,数据域用于存储数据,指针域用于存储下一个节点的指点,在数据域中存放此节点的数据,在指针域中存放下一节点的指针
其中:

  1. NULL为空指针,他不指向任何节点,只起标志作用,比如终端节点的指针域就是NULL
  2. head称为头指针变量,改变量的值是指向第一个节点的指针,由于单链表的每一个节点都要被一个指针指向,并且任何节点只能通过指向它的指针才能被引用,因此,会有一个头指针变量,指向第一个节点,再按照各个指针域中的指针找到所需节点
    单链表完整结构图解(包含头节点和终端节点):

    
    头指针head
      │
      ▼
    ┌─────────┐    ┌─────────┬───────┐    ┌─────────┬───────┐    ┌─────────┬───────┐    ┌─────────┬───────────────┐
    │  head   │──▶│  Data   │  Next │───▶│  Data   │  Next │───▶│  Data  │ Next  │───▶│  Data   │  Next(NULL) │───▶ NULL
    └─────────┘    └─────────┴───────┘    └─────────┴───────┘    └─────────┴───────┘    └─────────┴─────────────────┘
               头节点              节点1              节点2              节点3              终端节点
此时,我们仍然假设数据元素的类型为datatytpe,那么单链表结构定义如下:

typedef struct node * pointer;
struct node
{
datatype data; //data是节点数据域
pointer next; //next是节点指针域
};
typedef pointer lklist;

但是这样子看起来似乎不是很清水,此处我们给出C语言中的单链表结构定义:
```C
struct node                 
{
    int data;              
    struct node *next;    
}
int length = 0;

在上面这段代码中定义了一个node的结构体类型
data是节点数据域
next是节点指针域

随后我们将演示如何建立链表以及对链表的一些基本操作

1. 初始化链表

struct node *head;
head = NULL;

在上面这俩行代码中,我们定义了一个头指针变量head,并初始化为NULL,表示此时链表为空

2. 创建第一个节点

struct node *p;
p = (struct node *)malloc(sizeof(struct node));

在上面俩号代码中,我们先声明了node类型的指针变量p
随后我们使用malloc申请了一个内存空间,他的大小是xize参数为sizeof(struct node),即申请一个node结构体的大小的空间
同时,我们知道malloc申请的内存空间是一个无类型的,也就是返回一个void的指针,因此,我们使用强制类型转换将void转换为node*,并且将其赋给p

scanf ("%d", &p->data);
p->next = NULL;
head = p;
length++;

此处,我们从键盘得到一个数据,我们将他储存于p->data即节点数据域中
同时,将p->next指向NULL,表示此时p是链表的第一个节点,并且p是链表的终端节点
最后,将head指向p,表示此时链表头指针head指向第一个节点p

3. 创建第二个节点

struct  node *q;
q = (struct node *)malloc(sizeof(struct node));
scanf ("%d", &q->data);
q->next = NULL;
p->next = q;
length++;

在上面这段代码中,我们的操作与创建第一个节点大致相同,但是不一样的是,这里我们为了方便演示,使用的是p和q俩个不同的变量,这一点是不规范的,倘若我们要创建几百个节点,变量名远远不够,所以将会在此markdown后的Demo中给出一个较为标准的写法
对上方代码的解释如下:
首先创建一个node类型的指针变量q
创建一个内存空间,大小为sizeof(struct node),并强制类型转换将其转换为node
,将其赋给q
从键盘得到数据,将数据储存于q->data中
将q->next指向NULL,表示此时q是链表的终端节点
将p->next指向q,表示此时p是链表的第一个节点,并且p的next域指向q,表示p和q是相邻的

5. 链表数据的获取

对于数据的获取,我觉得应当加一个length参数,表示链表的长度,这样,我们就可以遍历整个链表,获取其中的数据,同时获取数据时可以避免越界问题,所以此时我们将对上方代码加上length参数,但是按照从上往下顺序阅读上方并没没有任何对length的说明,故此处做说明

5.1 单数据的获取

获取头元素

struct node *index;
index = head;
if (index == NULL) {
    printf("链表为空!n");
} else {
    printf("链表数据为:%dn", index->data);
}

获取链表中任意长度的元素

//首先为了避免冲突,我们事先规定我们的链表中存储的都是int类型的数据
struct node *index;
index = head;
int target = 2;//此处我们设置我们要获取到链表第二个er个节点的数据

for (int i = 1; i <= target; i++) {
    index = index->next;//移动到下一个节点
}

printf("链表数据为:%dn", index->data);
5.2 遍历链表

遍历链表,获取所有数据

struct node *index;
index = head;
while (index != NULL) {
    printf("链表数据为:%dn", index->data);
    index = index->next;
}

或者使用我们事先定义好的length

struct node *index;
index = head;
for (int i = 1; i <= length; i++) {
    printf("链表数据为:%dn", index->data);
    index = index->next;
}

6. 链表数据的插入

此处我们假设要将元素“3”插入到链表第2个位置上

// 将元素"3"插入到链表第2个位置上
struct node *index;
index = head;
int data = 3;
int target = 2;
struct node *targetNext;

// 移动到插入位置的前一个节点
for (int i = 1; i < target; i++) {
    index = index->next;
}

// 保存原节点的next域
targetNext = index->next;

// 创建新节点并插入
struct node *newNode;
newNode = (struct node *)malloc(sizeof(struct node));
newNode->data = data;
newNode->next = targetNext;
index->next = newNode;
length++;

(为什么我越写越晕)

7. 链表数据的删除与清空

7.1 删除数据
struct node *index;
index = head;
int target = 2;//目标是删除链表第2个节点
for (int i = 1; i <= target - 1; i++) {
    index = index->next;
}
struct node *targetNext;
targetNext = index->next;
length--;

删除数据时,我们首先将index移动到目标节点的前一个节点,然后,将index的next域指向目标节点的next域,即index的next域指向目标节点的next域的next域,这样,我们就删除了目标节点

7.2 清空链表
length = 0;
head = NULL;

清空链表时,我们直接将head指向NULL,表示此时链表为空

由于写的匆忙和技术不过娴熟,此文章可能存在一部分的错误,欢迎提出不同看法,我将及时学习并修改不当之处,感谢支持!

我的博客即将同步至腾讯云开发者社区,邀请大家一同入驻:https://cloud.tencent.com.cn/developer/support-plan?invite_code=1r244lfzquqoz

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇
皖ICP备2025092305号-1