数据结构一之顺序存储、链式存储、线性表

本文详细探讨了数据结构中的顺序存储、链式存储以及线性表的概念和操作。顺序存储结构是逻辑上相邻的元素物理位置相邻,适合随机存取,但插入和删除操作较慢。链式存储结构通过指针链接元素,插入和删除操作较快,但查找相对较慢。线性表包括顺序表和链表,具备一对一的逻辑关系。文章通过实例展示了单链表、静态链表和双向链表的创建、插入、删除、查找和更新操作,以及静态链表的备用链表机制。此外,文章还介绍了循环链表的应用,如约瑟夫环问题的解决。
摘要由CSDN通过智能技术生成

顺序存储

         顺序存储结构是存储结构类型中的一种,该结构是把逻辑上相邻的结点存储在物理位置上相邻的存储单元中,结点之间的逻辑关系由存储单元的邻接关系来体现。简单来说就是,用一段连续的地址存放数据元素,数据间的逻辑关系和物理关系相同。

         线性结构的特点就好比一串珠子,其特点是第一个节点只有一个后继,没有前驱,最后一个节点是只有一个前驱,没有后继。而其余的节点只有一个前驱和一个后继。说白了线性表就是一串。下方这个图就是线性表的示例图。中间蓝色的节点前方的是就是改点对应的前驱,后边就是改点对应的后继。从下方可以明确看出head没有前驱只有后继,而tail只有前驱没有后继。

  https://images2015.cnblogs.com/blog/545446/201609/545446-20160922183629059-2080886972.png

         顺序存储的优点:存储密度大,空间利用度高,比链式存储节约空间;存储操作上方便操作,顺序支持随机存取,查找会比较容易

         缺点:插入或者删除元素时不方便,花费的时间更多

往顺序线性表中插入数据

         见下图往B与C之间插入一个M,在插入之前我们需要将CD整体往后移一个位置,为M空出一个位置,再见M放入。

https://img2018.cnblogs.com/blog/1581679/201909/1581679-20190912175617509-1504046585.png

往顺序线性表中删除元素

         与上面所说的插入其实挺像的,前者在插入位置后的元素都往后移,后者则是向左移覆盖掉要删除的元素,需要注意的是,要将最后一个元素进行移除,可以参考下图

https://img2018.cnblogs.com/blog/1581679/201909/1581679-20190912180344717-1171728140.png

链式存储

         链式存储结构,又叫链接存储结构。在计算机中用一组任意的存储单元存储线性表的数据元素(这组存储单元可以是连续的,也可以是不连续的).它不要求逻辑上相邻的元素在物理位置上也相邻.因此它没有顺序存储结构所具有的弱点,但也同时失去了顺序表可随机存取的优点。

优点:插入或删除时方便些,空间使用灵活;存储密度小,空间利用度低

缺点:查找会相较顺序存储方式复杂一些,花费的时间会更多

往链式线性表中插入数据

(1)往链表的后方添加元素
https://img2018.cnblogs.com/blog/1581679/201909/1581679-20190912180808362-1749329821.png
这里我们先看图,其实就是将想要插入的元素往链表的尾部插入,然后更新一下为节点tail的位置即可。

(2)往链表的头部插入元素
https://img2018.cnblogs.com/blog/1581679/201909/1581679-20190912181227444-1886172126.png
看这个图我想你应该可以理解这句话,首先第一步需要我们的“C”去找组织中的A,第二步是头结点接到新元素C上。

往链式线性表中删除数据

https://img2018.cnblogs.com/blog/1581679/201909/1581679-20190912182709333-1518099807.png

要想移除单向链表中的一个元素,首先我们得找到被移除结点的前驱的位置,比如是pre“A”。当前移除的元素是remove“B”,让pre->next = remove->next, 然后再执行remove->next = NULL。经过上面这些步骤,B就与链表脱离关系了。

 

我们对比一下这两种存储方式的优缺点

一、从空间性能角度

(1)由下表可以看出顺序存储的存储密度是1(100%)。什么意思呢?就是开辟一段连续的空间,用来存顺序表,这一段空间所有的位置都用来存储我们需要的数据信息,没有空间的浪费。所以利用率达到了100%【也就是存储密度是1】。然而链式结构中的节点不仅存储了数据,还保存了指针,然而指针并没有存储我们用到的数据,所以说,链式存储的存储密度是小于1的。

(2)由下表可以看出顺序存储的容量分配要弱一些。顺序存储需要多少空间要是事先确定,链式存储是动态分配的,更加灵活吗,可以动态的更改分配。

二、从时间性能方面

(1)在时间性能反方面,查找运算用顺序表更方便,链式表相对来讲就差一些。当然,在内容没有顺序的前提下,他们的时间复杂度是一样的。都是从第一个开始匹配。如果存储的顺序表本身是有序的,涉及到二分查找法时,顺序存储要更优。

(2)读取信息(指定某个内容读取出来)顺序表更优。比如说要读取a[5],在顺序表中,直接读取a[5]即可,但是在链式存储中,假设指针在头结点,头结点指向1号节点, 要next()4次才能定位到a[5]。

(3)插入运算,链式结构更优。链式结构在插入数据的时候,只需要将要插入的节点的指针指向要插入节点上一个节点的next节点,在将上一个节点的next节点指向要插入的节点即可。但是在顺序结构中,需要将要插入位置之后的所有元素向后移,再插入新的节点、

(4)删除运算,链式结构更优。链式结构在删除节点是,只需要将要删除节点的上一个节点的指针指向他的下一个节点即可。然而在顺序结构中,需要将要删除节点之后的所有节点前移一个位置。

https://images2018.cnblogs.com/blog/1157942/201803/1157942-20180326185447296-1469130035.png

 

线性表

         线性表,全名为线性存储结构。线性表是n个数据特性相同的元素的组成有限序列,是最基本且常用的一种线性结构(线性表,栈,队列,串和数组都是线性结构),同时也是其他数据结构的基础。

线性表是最简单的数据结构,其主要特征有:

1、 每个线性表只有一个头元素,一个尾元素;

2、 除第一个数据元素外,每个元素都有一个直接前驱;

3、 除最后一个数据元素外,每个元素都有一个直接后继。

前驱和后继

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

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

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

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

图 4 前驱和后继

顺序表:

         顺序表,全名顺序存储结构,是线性表的一种。

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

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

需要自定义顺序表,代码如下:

typedef struct Table{

    int * head;//声明了一个名为head的长度不确定的数组,也叫“动态数组”

    int length;//记录当前顺序表的长度

    int size;//记录顺序表分配的存储容量

}table;

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

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

  • 给 head 动态数据申请足够大小的物理空间;
  • 给 size 和 length 赋初值;
#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;

}

程序运行结果如下:

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

顺序表的基本操作

顺序表插入元素

向已有顺序表中插入数据元素,根据插入位置的不同,可分为以下 3 种情况:

  1. 插入到顺序表的表头;
  2. 在表的中间位置插入元素;
  3. 尾随顺序表中已有元素,作为顺序表中的最后一个元素;

虽然数据元素插入顺序表中的位置有所不同,但是都使用的是同一种方式去解决,即:通过遍历,找到数据元素要插入的位置,然后做如下两步工作:

  • 将要插入位置元素以及后续的元素整体向后移动一个位置;
  • 将元素放到腾出来的位置上;

例如,在 {1,2,3,4,5} 的第 3 个位置上插入元素 6,实现过程如下:

  • 遍历至顺序表存储第 3 个数据元素的位置,如图 1 所示:



图 1 找到目标元素位置

  • 将元素 3 以及后续元素 4 和 5 整体向后移动一个位置,如图 2 所示:



图 2 将插入位置腾出

  • 将新元素 6 放入腾出的位置,如图 3 所示:

图 3 插入目标元素

因此,顺序表插入数据元素的 C 语言实现代码如下:

    //插入函数,其中,elem为插入的元素,add为插入到顺序表的位置
    table addTable(table t,int elem,int add)
    {
        //判断插入本身是否存在问题(如果插入元素位置比整张表的长度+1还大(如果相等,是尾随的情况),或者插入的位置本身不存在,程序作为提示并自动退出)
        if (add>t.length+1||add<1) {
            printf("插入位置有问题\n");
            return t;
        }
        //做插入操作时,首先需要看顺序表是否有多余的存储空间提供给插入的元素,如果没有,需要申请
        if (t.length==t.size) {
            t.head=(int *)realloc(t.head, (t.size+1)*sizeof(int));
            if (!t.head) {
                printf("存储分配失败\n");
                return t;
            }
            t.size+=1;
        }
        //插入操作,需要将从插入位置开始的后续元素,逐个后移
        for (int i=t.length-1; i>=add-1; i--) {
            t.head[i+1]=t.head[i];
        }
        //后移完成后,直接将所需插入元素,添加到顺序表的相应位置
        t.head[add-1]=elem;
        //由于添加了元素,所以长度+1
        t.length++;
        return t;
    }

注意,动态数组额外申请更多物理空间使用的是 realloc 函数。并且,在实现后续元素整体后移的过程,目标位置其实是有数据的,还是 3,只是下一步新插入元素时会把旧元素直接覆盖。

顺序表删除元素

从顺序表中删除指定元素,实现起来非常简单,只需找到目标元素,并将其后续所有元素整体前移 1 个位置即可。

后续元素整体前移一个位置,会直接将目标元素删除,可间接实现删除元素的目的。

例如,从 {1,2,3,4,5} 中删除元素 3 的过程如图 4 所示:


http://c.biancheng.net/uploads/allimg/190426/1G31532D-3.gif
图 4 顺序表删除元素的过程示意图


因此,顺序表删除元素的 C 语言实现代码为:

    table delTable(table t,int add){
        if (add>t.length || add<1) {
            printf("被删除元素的位置有误\n");
            return t;
        }
        //删除操作
        for (int i=add; i<t.length; i++) {
            t.head[i-1]=t.head[i];
        }
        t.length--;
        return t;
    }

顺序表查找元素

顺序表中查找目标元素,可以使用多种查找算法实现,比如说二分查找算法、插值查找算法等。
这里,我们选择顺序查找算法,具体实现代码为:

//查找函数,其中,elem表示要查找的数据元素的值
int selectTable(table t,int elem){
    for (int i=0; i<t.length; i++) {
        if (t.head[i]==elem) {
            return i+1;
        }
    }
    return -1;//如果查找失败,返回-1
}

顺序表更改元素

顺序表更改元素的实现过程是:

  1. 找到目标元素;
  2. 直接修改该元素的值;

顺序表更改元素的 C 语言实现代码为:

    //更改函数,其中,elem为要更改的元素,newElem为新的数据元素
    table amendTable(table t,int elem,int newElem){
        int add=selectTable(t, elem);
        t.head[add-1]=newElem;//由于返回的是元素在顺序表中
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值