线性表的讲解

一、定义

线性表是最基本、最简单、也是最常用的一种数据结构。线性表(linear list)是数据结构的一种,一个线性表是n个具有相同特性的数据元素的有限序列。

线性表中数据元素之间的关系是一对一的关系,即除了第一个和最后一个数据元素之外,其它数据元素都是首尾相接的(注意,这句话只适用大部分线性表,而不是全部。比如,循环链表逻辑层次上也是一种线性表(存储层次上属于链式存储,但是把最后一个数据元素的尾指针指向了首位结点)。

特点

1.集合中必存在唯一的一个“第一元素”。

2.集合中必存在唯一的一个 “最后元素” 。

3.除最后一个元素之外,均有唯一的后继(后件)。

4.除第一个元素之外,均有唯一的前驱(前件)。

分类

在数据结构逻辑层次上细分,线性表可分为一般线性表和受限线性表。一般线性表也就是我们通常所说的“线性表”,可以自由的删除或添加结点。受限线性表主要包括栈和队列,受限表示对结点的操作受限制。

线性表从存储结构上划分为顺序表链表,存储结构上章有讲到,想详细理解可看上章。

表名称存储结构性质
顺序表顺序存储逻辑相邻,物理也相邻
链表链式存储逻辑相邻,物理不一定相邻

二、顺序表

2.1 简介

将表中元素一个接一个的存入一组连续的存储单元中,这种存储结构是顺序结构。

采用顺序存储结构的线性表简称为 “顺序表”。
顺序表的存储特点是:只要确定了起始位置,表中任一元素的地址都通过下列公式得到:LOC(ai)=LOC(a1)+(i-1)*L ,其中1≤i≤n ,L是元素占用存储单元的长度。

顺序表的结构定义:

#define maxlen 50 //定义顺序表中元素个数最多有几个
typedef struct Table
{
    elementtype data[maxlen]; //elementtype是元素的类型 依具体情况而定
    int table; //便于时刻了解顺序表里元素的个数
}seqlist; //顺序表的名称

声明顺序表类型变量:seqlist L,L1;
如顺序表的每个结点占用len个内存单元,用location (ki)表示顺序表中第i个结点ki所占内存空间的第1个单元的地址。则有如下的关系:location (ki+1) = location (ki) +len
location (ki) = location(k1) + (i-1)len
存储结构要体现数据的逻辑结构,顺序表的存储结构中,内存中物理地址相邻的结点一定具有顺序表中的逻辑关系。 

2.2 C语言中实际使用(基本操作)

#include <stdio.h>
#include <stdlib.h>

#define maxlen 8 //对maxlen进行宏定义,定义顺序表中元素个数最多有几个

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

seqlist initseqlist(){
    seqlist t;
    t.head=(int*)malloc(maxlen*sizeof(int)); //构造一个空的顺序表,动态申请存储空间
    if (!t.head) //如果申请失败,作出提示并直接退出程序
    {
        printf("初始化失败\n");
        exit(0);
    }
    t.length=0; //空表的长度初始化为0
    t.size=maxlen; //空表的初始存储空间为maxlen
    return t;
}

//向顺序表中的某个位置添加元素
seqlist addseqlist(seqlist 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");
        }
        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;
}

//删除顺序表中某个的位置
seqlist delseqlist(seqlist 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;
}

//查询顺序表中某个元素的位置
int selectseqlist(seqlist t,int elem){
    for (int i=0; i<t.length; i++) {
        if (t.head[i]==elem) {
            return i+1;
        }
    }
    return -1; //如果查找失败,返回-1
}

//修改顺序表中某个的元素改成另一个元素
seqlist amendseqlist(seqlist t,int elem,int newElem){
    int add=selectseqlist(t, elem);
    t.head[add-1]=newElem; //由于返回的是元素在顺序表中的位置,所以-1就是该元素在数组中的下标
    return t;
}

//输出顺序表中元素的函数
void displayseqlist(seqlist t){
    for (int i=0;i<t.length;i++) {
        printf("%d ",t.head[i]);
    }
    printf("\n");
}
int main(){
    seqlist t1=initseqlist();
    
    //向顺序表中添加元素
    for (int i=1; i<=maxlen; i++) {
        t1.head[i-1]=i;
        t1.length++;
    }
    
    printf("原顺序表:\n");
    displayseqlist(t1);
  
    printf("删除第1个位置:\n");
    t1=delseqlist(t1, 1);
    displayseqlist(t1);
  
    printf("在第2的位置插入元素5:\n");
    t1=addseqlist(t1, 5, 2);
    displayseqlist(t1);
  
    printf("查找元素6的位置:\n");
    int add=selectseqlist(t1, 6);
    printf("%d\n",add);
  
    printf("将元素3改为6:\n");
    t1=amendseqlist(t1, 3, 6);
    displayseqlist(t1);
    return 0;
}

输出结果:

三、链表

3.1 简介

链表有很多种不同的类型:单向链表,双向链表以及循环链表,下面以单向链表为例。

线性表的链式存储表示的特点是用一组任意的存储单元存储线性表的数据元素(这组存储单元可以是连续的,也可以是不连续的)。因此,为了表示每个数据元素 与其直接后继数据元素之间的逻辑关系,对数据元素来说,除了存储其本身的信息之外,还需存储一个指示其直接后继的信息(即直接后继的存储位置)。由这两部分信息组成一个"结点"(如概述旁的图所示),表示线性表中一个数据元素。线性表的链式存储表示,有一个缺点就是要找一个数,必须要从头开始找起,十分麻烦。

根据情况,也可以自己设计链表的其它扩展。但是一般不会在边上附加数据,因为链表的点和边基本上是一一对应的(除了第一个或者最后一个节点,但是也不会产生特殊情况)。不过有一个特例是如果链表支持在链表的一段中把前和后指针反向,反向标记加在边上可能会更方便。

对于非线性的链表,可以参见相关的其他数据结构,例如树、图。另外有一种基于多个线性链表的数据结构:跳表,插入、删除和查找等基本操作的速度可以达到O(nlogn),和平衡二叉树一样。

数据元素是随机存储,并通过指针表示数据之间逻辑关系的存储结构就是链式存储结构。

其中存储数据元素信息的域称作数据域(设域名为data),存储直接后继存储位置的域称为指针域(设域名为next)。指针域中存储的信息又称做指针或链。

由分别表示 1,2,… ,n 的N 个结点依次相链构成的链表,称为线性表的链式存储表示,由于此类链表的每个结点中只包含一个指针域,故又称单链表或线性链表。

3.2 C语言中实际使用(基本操作)

#include <stdio.h>
#include <stdlib.h>

typedef struct Link {
    int  elem; //代表数据域
    struct Link *next; //代表指针域,指向直接后继元素
}link;  //link为节点名,每个节点都是一个 link 结构体

link* initLink();

//链表插入的函数,p是链表,elem是插入的结点的数据域,add是插入的位置
link * insertElem(link * p, int elem, int add);

//删除结点的函数,p代表操作链表,add代表删除节点的位置
link * delElem(link * p, int add);

//查找结点的函数,elem为目标结点的数据域的值
int selectElem(link * p, int elem);

//更新结点的函数,newElem为新的数据域的值
link * amendElem(link * p, int add, int newElem);
void display(link *p);

int main() {
    //初始化链表
    printf("初始化链表为:\n");
    link *p = initLink();
    display(p);

    printf("在第4的位置插入元素5:\n");
    p = insertElem(p, 5, 4);
    display(p);

    printf("删除元素3:\n");
    p = delElem(p, 3);
    display(p);

    int address = selectElem(p, 6);
    if (address == -1) {
        printf("没有该元素");
    }
    else {
        printf("查找元素6的位置为:\n%d\n", address);
    }
    printf("更改第3的位置上的数据为7:\n");
    p = amendElem(p, 3, 7);
    display(p);

    return 0;
}

link * initLink() {
    link * p = (link*)malloc(sizeof(link));//创建一个头结点
    link * temp = p;//声明一个指针指向头结点,用于遍历链表
    
    //生成链表
    for (int i = 1; i < 9; i++) {
        link *a = (link*)malloc(sizeof(link));
        a->elem = i;
        a->next = NULL;
        temp->next = a;
        temp = temp->next;
    }
    return p;
}
link * insertElem(link * p, int elem, int add) {
    link * temp = p;//创建临时结点temp
    
    //首先找到要插入位置的上一个结点
    for (int i = 1; i < add; i++) {
        temp = temp->next;
        if (temp == NULL) {
            printf("插入位置无效\n");
            return p;
        }
    }
    
    //创建插入结点c
    link * c = (link*)malloc(sizeof(link));
    c->elem = elem;
    
    //向链表中插入结点
    c->next = temp->next;
    temp->next = c;
    return  p;
}

link * delElem(link * p, int add) {
    link * temp = p;
    
    //遍历到被删除结点的上一个结点
    for (int i = 1; i < add; i++) {
        temp = temp->next;
        if (temp->next == NULL) {
            printf("没有该结点\n");
            return p;
        }
    }
    link * del = temp->next;//单独设置一个指针指向被删除结点,以防丢失
    temp->next = temp->next->next;//删除某个结点的方法就是更改前一个结点的指针域
    free(del);//手动释放该结点,防止内存泄漏
    return p;
}
int selectElem(link * p, int elem) {
    link * t = p;
    int i = 1;
    while (t->next) {
        t = t->next;
        if (t->elem == elem) {
            return i;
        }
        i++;
    }
    return -1;
}
link *amendElem(link * p, int add, int newElem) {
    link * temp = p;
    temp = temp->next;//tamp指向首元结点
    
    //temp指向被删除结点
    for (int i = 1; i < add; i++) {
        temp = temp->next;
    }
    temp->elem = newElem;
    return p;
}
void display(link *p) {
    link* temp = p;//将temp指针重新指向头结点
    
    //只要temp指针指向的结点的next不是Null,就执行输出语句。
    while (temp->next) {
        temp = temp->next;
        printf("%d ", temp->elem);
    }
    printf("\n");
}

输出结果:

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

m0_68949064

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值