数据结构与算法总结——线性表

目 录

2 线性表

2.1 线性表的定义

2.2 线性表的基本操作

2.2 顺序表

2.2.1 顺序表的定义

2.2.2 顺序表的基本操作

2.3 链式表

2.3.1 链表的定义

2.3.2 链表的分类

2.3.3 单向链表

2.3.3.1 结点设计

2.3.3.2 链表的初始化

2.3.3.3 数据的插入

2.3.3.4 数据的删除

2.3.3.5 销毁链表


2 线性表

2.1 线性表的定义

在线性表中,我们将会学习常用的顺序表和链表,使用的语言为C语言,其中包含大量结构体以及指针的内容,务必掌握以上知识,否则理解后面知识会难以接受。

线性表是具有相同数据类型的n(n≥0)个数据元素的有限序列,其中n为表长,当n=0时线性表是一个空表。若用L命名线性表,则其一般表示为:

L = (a1,a2,...,an)

其中:a1是表头元素,an是表尾元素

重点除第一个元素外,每个元素有且仅有一个直接前驱;除最后一个元素外,每个元素有且仅有一个直接后继

2.2 线性表的基本操作

后面的顺序表以及链表也基本是以下操作,下面操作均以常用的链表为介绍,在此文章不用深究,在链表部分会细讲

(1)定义管理结构体:

一般分为数据域以及指针域

(2)初始化表:

构造一个空的线性表

init:初始化

一般会对初始化表进行函数封装,构造一个空的线性表myList,为其分配内存空间。

(3)对表进行插入操作:

insert:插入

在表myList中插入元素data,在链表中不必考虑数据位置,顺序表需要考虑位置。

(4)对表的值进行删除操作

delete:删除

删除表中的data元素。

(5)对表进行遍历操作

对表遍历后,可以遍历出表中的每个元素。

(6)对表进行销毁操作

destroy:销毁

销毁该线性表(链表),并释放该线性表在程序中所占用的内存空间。

以上操作为线性表的基本操作,你也可以自己封装一些如按值查找、求表长等操作。

2.2 顺序表

2.2.1 顺序表的定义

顺序表:顺序存储的线性表。

顺序存储就是将数据存储到一片连续的内存中,在C语言中,你也可以将数组当成顺序表,数组就是一种常见的顺序表。

 

2.2.2 顺序表的基本操作

顺序表设计

下面由我们来设计顺序表:

(1)定义管理结构体

一般而言,为了方便操作顺序表,需要一个专门管理顺序表的”管理结构体“,管理结构体中一般会包含:顺序表的总容量(数据域),顺序表的下标。

typedef 的作用是将定义这个结构体可以区别名,在此结构体我取了别名*p_list,则后续定义结构体指针可以简略写出成p_list plist

typedef struct seq_list {
    //数据域
    int data[5];
    //下标域
    int n;
}*p_list;

(2)初始化顺序表

所谓初始化就是建立一个不包含任何元素的顺序表,设置好管理结构体中的表的总容量、末元素下标等系列准备工作。

​
void init_list(p_list plist){
    //清空数据域
    plist->data[0] = 0;
    plist->data[1] = 0;
    plist->data[2] = 0;
    plist->data[3] = 0;
    plist->data[4] = 0;

    //设置下标域-1  数组是下标0开始存数据
    plist->n = -1;
}

​

(3)插入数据进顺序表 

​
//插入数据---头插
void insert_head_to_list(struct seq_list *plist,int i_data)
{
    //顺序表是否已满
    if(plist->n == 4)
    {
        printf("list is full\n");
        return;
    }
    //未满就插入新的数据
    //给新数据腾位置---把已有的数据向后移动一个位置
    (plist->n)++;//下标加1
    for(int i =(plist->n);i>0;i--)
    {
        plist->data[i] = plist->data[i-1];
    }
    //放入新的数据
    plist->data[0] = i_data;
}

​

(4)删除数据

//删除目标数据
void del_data_from_list(p_list plist,int i_data)
{
    //检测顺序表是否为空
    if(plist->n == -1)
    {
        printf("list is empty\n");
        return;
    }
    //定位---找到目标数据的位置
    int pos = -1;//保存目标数据所在的下标
    for(int i=0;i<=(plist->n);i++)
    {
        if(plist->data[i]== i_data)
        {
            pos = i;
            break;
        }
    }
    if(pos == -1)
    {
        printf("no this data\n");
        return;
    }
    //2.删除---移位覆盖删除法
    for(int i = pos;i<(plist->n);i++)
    {
        plist->data[i] = plist->data[i+1];
    }
    plist->data[plist->n] = 0;//将删除的值清0
    (plist->n)--;//下标往前移动一位

}

(5)遍历顺序表

当你想要对插入或删除的结果遍历输出时,可以参考以下代码

//遍历输出
void show_list(p_list plist)
{
    //如果顺序表为空,给提示信息
    if(plist == NULL){
        printf("list is NULL");
        return;
    }
    //否则遍历循序表
    for(int i=0;i<=(plist->n);i++)
    {
        printf("%d ",plist->data[i]);
    }
    printf("\n");
}

在主函数中使用函数接口:

在主函数中,我们定义了一个mylist顺序表,并对其进行初始化,写了一个对顺序表进行插入和删除的while循环,在程序中我们可以输入0或者按住Ctrl+C就可以退出对顺序表的操作,如果我们输出一个数,进行插入操作并遍历输出,反之输出这个数的相反数则进行删除操作并遍历输出,你也可以设计自己更利于理解的方法。

int main(){
    int i_data = 0;
    //定义一个顺序表
    list mylist;

    //初始化
    init_list(&mylist);

    //操作     大于0 插入数据  小于 0 删除 这个数绝对值
    while(1)
    {
        scanf("%d",&i_data);//输入目标数据
        if(i_data > 0)
        {
            //insert_tail_to_list(&mylist,i_data);//尾插
            insert_head_to_list(&mylist,i_data);//头插
        }
        if(i_data < 0)
        {
            del_data_from_list(&mylist,-i_data);//删除
        }
        if(i_data == 0)
        {
            break;
        }
        show_list(&mylist);//遍历打印
    }
}

操作结果:

2.3 链式表

(重点!!!!!)

2.3.1 链表的定义

顺序存储中的数据因为挤在一起而导致需要成片移动,那很容易想到的解决方案是将数据离散地存储在不同内存块中,然后在用来指针将它们串起来。在逻辑上他们是线性存储的,这种朴素的思路所形成的链式线性表,就是所谓的链表。

顺序表和链表在内存在的基本样态如下图所示:

逻辑上是线性存储,用图可以这样理解,用一条条线串起来组成他们的关系,其中的线一般都是指针,它在内存中的存储是不一定连续的。

单向链表

2.3.2 链表的分类

根据链表中各个节点之间使用指针的个数,以及首尾节点是否相连,可以将链表细分为如下种类:

  1. 单向链表
  2. 单向循环链表
  3. 双向链表
  4. 双向循环链表

这些不同链表的操作都是差不多的,只是指针数目的异同。以最简单的单向链表为例,其基本示意图如下所示:

0

2.3.3 单向链表

顾名思义,单向链表即只有一个头结点,以及指向下一个结点的指针。

        上图中,所有的节点均保存一个指针,指向其逻辑上相邻的下一个节点(末尾节点指向空)。另外注意到,整条链表用一个所谓的头指针 head 来指向,由 head 开始可以找到链表中的任意一个节点。head 通常被称为头指针。

链表的基本操作,一般包括:

  1. 节点设计
  2. 初始化链表节点
  3. 增删查改节点
  4. 链表遍历
  5. 销毁链表

下面着重针对这几项常见操作,讲解单向链表的处理。

2.3.3.1 结点设计

我们将定义一个单向链表的管理结构体,通常包含数据域和指针域,在后续我们对结点进行增删改查等操作通过使用指针域会更方便

管理结构体代码:

typedef struct node{
    int data;//数据域
    struct node *next;//指针域
}*list;
2.3.3.2 链表的初始化

任何的链表都可以使用无头结点或有头结点的形式,但是一般情况下不推荐使用无头结点的链表,因为在操作的过程中很容易造成断链,即头结点不存放数据。

链表初始化封装函数代码:

//初始化链表
list Init_List(){
    list head = malloc(sizeof (list));//为头结点申请内存空间

    if(head == NULL){
        printf("malloc error");
        return NULL;
    }//若申请内存空间失败,返回NULL并打印失败信息

    //初始化头结点
    head->data = 0;
    head->next = NULL;

    return head;//返回头结点
}

malloc作用:申请内存空间,括号内是内存的大小。

我们对链表进行初始化,首先需要对头结点申请一段内存空间,才能创建链表,然后将头结点的数据清空并让它的指针域指向空,最后将头结点返回即可初始化一个只有头结点的链表。

2.3.3.3 数据的插入

数据的插入有两种方法,一种是头插法,另一种是尾插法,头插法是将数据插入头结点之前,尾插法是将数据插入链表的尾部。

头插法:

我们将新的结点指向头结点的下一个结点,并将头结点与旧结点断开,指向新的结点。

注意(重点):头插法插入必须先指向头结点的后继结点,才能将头结点指向新结点!!顺序不能调换!!(俗称:先建立新关系,再断开旧关系)

代码实现:

//头插法
int Insert_node(list head,int data){
    list new = malloc(sizeof (struct node));//为新结点申请内存空间
    new->data = data;//将插入的数据给进新结点的数据域
    
    if(head == NULL)
    {
        printf("malloc error!\n");
        return -1;//申请内存空间失败
    }
    
    new->next = head->next;//建立新关系
    head->next = new;//断开旧关系
    
    return 1;//插入成功
}

尾插法:

顾名思义,将数据结点插入到链表的最后一个位置,这个操作我们需要遍历整个链表,才能将数据结点插入,其方法跟头插法大同小异,只不过需要遍历链表。

//尾插法
int Insert_node_back(list head,int data){

    list new = malloc(sizeof (list));//为新结点申请内存空间

    if(head == NULL){
        printf("malloc error!\n");
        return -1;//申请内存空间失败
    }

    new->data = data;//将插入的数据给进新结点的数据域

    list p = head;//建立一个结点p指向

    while(p->next!=NULL){
        p = p->next;
    }//遍历整个链表

    new->next = NULL;//建立新关系
    p->next = new;//断开旧关系

    return 1;//插入成功
}
2.3.3.4 数据的删除

结点删除的步骤:

       1.判断链表是否为空

       2.定位

       3.删除节点关系

       4.释放空间

删除结点代码实现:

//删除目标数据
int del_data_list(list head,int data){
//检测链表是否为空
    if(head->next == NULL)
    {
        printf("list is empty\n");
        return 0;
    }
    //定位---找到目标数据的位置
    list pos = NULL;
    list temp = NULL;
    for(pos = head;pos->next!=NULL;pos = pos->next )
    {
        if((pos->next)->data == data)
        {
            temp = pos->next;//要删除的目标节点
            break;
        }
    }

    //判断是否找到目标节点
    if(pos->next==NULL)
    {
        printf("no this node\n");
        return -1;
    }

    //删除关系
    pos->next = temp->next;
    temp->next = NULL;

    //释放节点空间
    free(temp);

    return 1;
}

注意定位的时候需要遍历链表才能找到需要删除的数据结点,这也是链表的缺点

2.3.3.5 销毁链表

只需要将链表遍历并释放每个结点即可销毁链表,使用完链表后一般都要将其销毁并释放内存空间,否则容易出现内存泄漏等问题。

// 销毁链表
void Destroy_list(struct node *head) {
    struct node *temp;
    while (head != NULL) {
        temp = head;
        head = head->next;
        free(temp);
    }
}

链表的操作结果与如上顺序表类似,可参考顺序表操作结果

其他链表我将在下一章节进行一一讲解。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值