线性表,双向链表,静态链表,循环链表(约瑟夫环)(上)

简介: 线性表,双向链表,静态链表,循环链表(约瑟夫环)

什么是线性表(线性存储结构)


通过前面的学习我们知道,具有 “一对一”逻辑关系的数据,最佳的存储方式是使用线性表。那么,什么是线性表呢?


线性表,全名为线性存储结构。使用线性表存储数据的方式可以这样理解,即“把所有数据用一根线儿串起来,再存储到物理空间中”。

  image.png

图 1 "一对一"逻辑关系的数据


如图 1 所示,这是一组具有“一对一”关系的数据,我们接下来采用线性表将其储存到物理空间中。

首先,用“一根线儿”把它们按照顺序“串”起来,如图 2 所示:

image.png

图 2 数据的"线性"结构


图 2 中,左侧是“串”起来的数据,右侧是空闲的物理空间。把这“一串儿”数据放置到物理空间,我们可以选择以下两种方式,如图 3 所示。

image.png

图 3 两种线性存储结构


图 3a) 是多数人想到的存储方式,而图 3b) 却少有人想到。我们知道,数据存储的成功与否,取决于是否能将数据完整地复原成它本来的样子。如果把图 3a) 和图 3b) 线的一头扯起,你会发现数据的位置依旧没有发生改变(和图 1 一样)。因此可以认定,这两种存储方式都是正确的。


将具有“一对一”关系的数据“线性”地存储到物理空间中,这种存储结构就称为线性存储结构(简称线性表)。


使用线性表存储的数据,如同向数组中存储数据那样,要求数据类型必须一致,也就是说,线性表存储的数据,要么全部都是整形,要么全部都是字符串。一半是整形,另一半是字符串的一组数据无法使用线性表存储。


顺序存储结构链式存储结构


图 3 中我们可以看出,线性表存储数据可细分为以下 2 种:


  1. 如图 3a) 所示,将数据依次存储在连续的整块物理空间中,这种存储结构称为顺序存储结构 (简称顺序表);
  2. 如图 3b) 所示,数据分散的存储在物理空间中,通过一根线保存着它们之间的逻辑关系,这种存储结构称为链式存储结构(简称链表);


也就是说,线性表存储结构可细分为顺序存储结构和链式存储结构。


前驱和后继


数据结构中,一组数据中的每个个体被称为“数据元素”(简称“元素”)。例如,图 1 显示的这组数据,其中 1、2、3、4 和 5 都是这组数据中的一个元素。

另外,对于具有“一对一”逻辑关系的数据,我们一直在用“某一元素的左侧(前边)或右侧(后边)”这样不专业的词,其实线性表中有更准确的术语:


  • 某一元素的左侧相邻元素称为“直接前驱”,位于此元素左侧的所有元素都统称为“前驱元素”;
  • 某一元素的右侧相邻元素称为“直接后继”, 位于此元素右侧的所有元素都统称为“后继元素”;


以图 1 数据中的元素 3 来说,它的直接前驱是 2 ,此元素的前驱元素有 2 个,分别是 1 和 2;同理,此元素的直接后继是 4 ,后继元素也有 2 个,分别是 4 和 5。如图 4 所示:

image.png  

图 4 前驱和后继


顺序表(顺序存储结构)及初始化详解


顺序表,全名顺序存储结构,是线性表的一种。通过《什么是线性表》一节的学习我们知道,线性表用于存储逻辑关系为“一对一”的数据,顺序表自然也不例外。


不仅如此,顺序表对数据的物理存储结构也有要求。顺序表存储数据时,会提前申请一整块足够大小的物理空间,然后将数据依次存储起来, 存储时做到数据元素之间不留一丝缝隙。


例如,使用顺序表存储集合 {1,2,3,4,5},数据最终的存储状态如图 1 所示:

image.png

图 1 顺序存储结构示意图


由此我们可以得出,将“具有 '一对一' 逻辑关系的数据按照次序连续存储到一整块物理空间上”的存储结构就是顺序存储结构。


通过观察图 1 中数据的存储状态,我们可以发现,顺序表存储数据同数组非常接近。其实,顺序表存储数据使用的就是数组。


顺序表的初始化


使用顺序表存储数据之前,除了要申请足够大小的物理空间之外,为了方便后期使用表中的数据,顺序表还需要实时记录以下 2 项数据:


  1. 顺序表申请的存储容量;
  2. 顺序表的长度,也就是表中存储数据元素的个数;


提示:正常状态下,顺序表申请的存储容量要大于顺序表的长度。

因此,我们需要自定义顺序表,C 语言实现代码如下:

typedef struct Table{
int * head;//声明了一个名为head的长度不确定的数组,也叫“动态数组”
int length;//记录当前顺序表的长度
int size;//记录顺序表分配的存储容量
}table;

注意,head 是我们声明的一个未初始化的动态数组,不要只把它看做是普通的指针。


接下来开始学习顺序表的初始化,也就是初步建立一个顺序表。建立顺序表需要做如下工作:


  • 给 head 动态数据申请足够大小的物理空间;
  • 给 size 和 length 赋初值;


因此,C 语言实现代码如下:

#define Size 5 //对Size进行宏定义,表示顺序表申请空间的大小
table initTable(){
table t;
t.head=(int*)malloc(Size*sizeof(int));//构造一个空的顺序表,动态申请存储空间
if (!t.head) //如果申请失败,作出提示并直接退出程序
{
printf("初始化失败");
exit(0);
}
t.length=0;//空表的长度初始化为0
t.size=Size;//空表的初始存储空间为Size
return t;
}

我们看到,整个顺序表初始化的过程被封装到了一个函数中,此函数返回值是一个已经初始化完成的顺序表。这样做的好处是增加了代码的可用性,也更加美观。与此同时,顺序表初始化过程中,要注意对物理空间的申请进行判断,对申请失败的情况进行处理,这里只进行了“输出提示信息和强制退出”的操作,可以根据你自己的需要对代码中的 if 语句进行改进。


通过在主函数中调用 initTable 语句,就可以成功创建一个空的顺序表,与此同时我们还可以试着向顺序表中添加一些元素,C 语言实现代码如下:

#include <stdio.h>
#include <stdlib.h>
#define Size 5
typedef struct Table{
int * head;
int length;
int size;
}table;
table initTable(){
table t;
t.head=(int*)malloc(Size*sizeof(int));
if (!t.head)
{
printf("初始化失败");
exit(0);
}
t.length=0;
t.size=Size;
return t;
}
//输出顺序表中元素的函数
void displayTable(table t){
for (int i=0;i<t.length;i++) {
printf("%d ",t.head[i]);
}
printf("\n");
}
int main(){
table t=initTable();
//向顺序表中添加元素
for (int i=1; i<=Size; i++) {
t.head[i-1]=i;
t.length++;
}
printf("顺序表中存储的元素分别是:\n");
displayTable(t);
return 0;
}

程序运行结果如下:


顺序表中存储的元素分别是:


1 2 3 4 5


可以看到,顺序表初始化成功。

 

双向链表及创建(C语言)详解


目前我们所学到的链表,无论是动态链表还是静态链表,表中各节点中都只包含一个指针(游标),且都统一指向直接后继节点,通常称这类链表为单向链表(或单链表)。


虽然使用单链表能 100% 解决逻辑关系为 "一对一" 数据的存储问题,但在解决某些特殊问题时,单链表并不是效率最优的存储结构。比如说,如果算法中需要大量地找某指定结点的前趋结点,使用单链表无疑是灾难性的,因为单链表更适合 "从前往后" 找,而 "从后往前" 找并不是它的强项。


为了能够高效率解决类似的问题,本节来学习双向链表(简称双链表)。

从名字上理解双向链表,即链表是 "双向" 的,如图 1 所示:

image.png

图 1 双向链表结构示意图


双向,指的是各节点之间的逻辑关系是双向的,但通常头指针只设置一个,除非实际情况需要

从图 1 中可以看到,双向链表中各节点包含以下 3 部分信息(如图 2 所示):


  1. 指针域:用于指向当前节点的直接前驱节点;
  2. 数据域:用于存储数据元素。
  3. 指针域:用于指向当前节点的直接后继节点;

image.png

图 2 双向链表的节点构成


因此,双链表的节点结构用 C 语言实现为:

typedef struct line{
struct line * prior; //指向直接前趋
int data;
struct line * next; //指向直接后继
}line;

双向链表的创建


同单链表相比,双链表仅是各节点多了一个用于指向直接前驱的指针域。因此,我们可以在单链表的基础轻松实现对双链表的创建。


需要注意的是,与单链表不同,双链表创建过程中,每创建一个新节点,都要与其前驱节点建立两次联系,分别是:


  • 将新节点的 prior 指针指向直接前驱节点;
  • 将直接前驱节点的 next 指针指向新节点;


这里给出创建双向链表的 C 语言实现代码:

line* initLine(line * head){
head=(line*)malloc(sizeof(line));//创建链表第一个结点(首元结点)
head->prior=NULL;
head->next=NULL;
head->data=1;
line * list=head;
for (int i=2; i<=3; i++) {
//创建并初始化一个新结点
line * body=(line*)malloc(sizeof(line));
body->prior=NULL;
body->next=NULL;
body->data=i;
list->next=body;//直接前趋结点的next指针指向新结点
body->prior=list;//新结点指向直接前趋结点
list=list->next;
}
return head;
}

我们可以尝试着在 main 函数中输出创建的双链表,C 语言代码如下:

#include <stdio.h>
#include <stdlib.h>
//节点结构
typedef struct line{
struct line * prior;
int data;
struct line * next;
}line;
//双链表的创建函数
line* initLine(line * head);
//输出双链表的函数
void display(line * head);
int main() {
//创建一个头指针
line * head=NULL;
//调用链表创建函数
head=initLine(head);
//输出创建好的链表
display(head);
//显示双链表的优点
printf("链表中第 4 个节点的直接前驱是:%d",head->next->next->next->prior->data);
return 0;
}
line* initLine(line * head){
//创建一个首元节点,链表的头指针为head
head=(line*)malloc(sizeof(line));
//对节点进行初始化
head->prior=NULL;
head->next=NULL;
head->data=1;
//声明一个指向首元节点的指针,方便后期向链表中添加新创建的节点
line * list=head;
for (int i=2; i<=5; i++) {
//创建新的节点并初始化
line * body=(line*)malloc(sizeof(line));
body->prior=NULL;
body->next=NULL;
body->data=i;
//新节点与链表最后一个节点建立关系
list->next=body;
body->prior=list;
//list永远指向链表中最后一个节点
list=list->next;
}
//返回新创建的链表
return head;
}
void display(line * head){
line * temp=head;
while (temp) {
//如果该节点无后继节点,说明此节点是链表的最后一个节点
if (temp->next==NULL) {
printf("%d\n",temp->data);
}else{
printf("%d <-> ",temp->data);
}
temp=temp->next;
}
}

程序运行结果:


1 <-> 2 <-> 3 <-> 4 <-> 5


链表中第 4 个节点的直接前驱是:3


线性表,双向链表,静态链表,循环链表(约瑟夫环)(下):https://developer.aliyun.com/article/1471359

目录
相关文章
|
6月前
|
存储 缓存 算法
数据结构和算法学习记录——总结顺序表和链表(双向带头循环链表)的优缺点、CPU高速缓存命中率
数据结构和算法学习记录——总结顺序表和链表(双向带头循环链表)的优缺点、CPU高速缓存命中率
56 0
|
6月前
|
算法
数据结构和算法学习记录——线性表之双向链表(上)-结点类型定义、初始化函数、创建新结点函数、尾插函数、打印函数、尾删函数
数据结构和算法学习记录——线性表之双向链表(上)-结点类型定义、初始化函数、创建新结点函数、尾插函数、打印函数、尾删函数
52 0
|
2月前
|
算法 Java
数据结构与算法学习六:单向环形链表应用实例的约瑟夫环问题
这篇文章通过单向环形链表的应用实例,详细讲解了约瑟夫环问题的解决方案,并提供了Java代码实现。
24 0
|
4月前
|
存储 Java
|
5月前
链表8(法二考试专用)7-8 sdut-C语言实验-双向链表
链表8(法二考试专用)7-8 sdut-C语言实验-双向链表
21 0
|
5月前
sdut 链表 8 -----7-8 sdut-C语言实验-双向链表
sdut 链表 8 -----7-8 sdut-C语言实验-双向链表
20 0
|
6月前
|
存储 缓存
【海贼王的数据航海】链表—双向链表
【海贼王的数据航海】链表—双向链表
30 0
|
6月前
|
存储 算法 前端开发
【C/数据结构与算法】:链表的实现(单向链表+双向链表)
【C/数据结构与算法】:链表的实现(单向链表+双向链表)
30 0
【循环链表】数据结构——单向循环链表和双向循环链表操作&笔记
【循环链表】数据结构——单向循环链表和双向循环链表操作&笔记
|
6月前
|
算法
数据结构和算法学习记录——线性表之双向链表(下)-头插函数、头删函数、查找函数、pos位置之前插入结点、pos位置删除结点及其复用、销毁链表函数
数据结构和算法学习记录——线性表之双向链表(下)-头插函数、头删函数、查找函数、pos位置之前插入结点、pos位置删除结点及其复用、销毁链表函数
32 0