线性表-单链表-数组

本文详细介绍了线性表的两种主要存储结构——顺序存储和链式存储。顺序存储利用一维数组实现,查找效率高但插入删除操作需要移动元素;链式存储则以结点形式存储,插入删除灵活但需要额外的指针存储空间。文章还对比了两者的优缺点,并给出了C语言实现的示例代码,帮助理解这两种数据结构的操作。

线性表

顺序表和链表

抽象数据结构-数据结合和操作集合

头结点:不存放数据元素的第一个结点

首元结点:存放数据元素的第一个结点

一、线性表的定义

零个或多个数据元素的有限序列

元素之间是有顺序的,如果存在多个元素,则第一个元素无前驱,最后一个元素无后继。其他每一个元素都有且只有一个前驱和一个后继。

在较复杂的线性表中,一个数据元素可以由若干个数据项组成

二、线性表的顺序存储结构

用一段地址连续的存储单元依次存储线性表的数据元素-物理地址连续

1.一般用一维数组来实现顺序存储结构

//最大存储容量
#define MAXSIZE 20
typedef struct Array
{
    int data[MAXSIZE];//数组存储数据元素(也可以使用指针)int* element
    int length;//线性表当前长度
    int Size;//线性表最大容量
}Array
;
​

2.描述顺序存储结构需要三个属性

存储空间的起始位置:数组data的存储位置就是存储空间的存储位置

线性表最大存储容量:数组长度MAXSIZE

线性表的当前长度:length

存储器中的每个存储单元都有自己的编号,这个编号就是地址

3.初始化线性表

//初始化
Array init_array()
{
    //定义一个抽象的数据类型
    Array array;
    array.length = 0;
    array.Size = MAXSIZE;
    return array;
}

4.查找元素

//查找元素-返回元素所在下标(也可以根据下标查找,返回元素值)
int find_ArrayElement(int find_key, Array* array)
{
    if (array->length == 0)
    {
        printf("查找错误:该表为空表!\n");
        return -1;
    }
    for (int i = 0; i < array->length; i++)
    {
        if (array->data[i] == find_key)
        {
            return i;
        }
    }
    //没有找到元素
    return -1;
}
​
//根据下标查找
void find_ArrayIndex(int index,Array* array)
{
    //判断下标是否合理
    if(index<0||index>=array->length)
    {
        printf("查找错误:下标错误!\n");
        return;
    }
    return array->data[index];
}

5.插入操作

思路:

1.插入位置不合理,抛出异常

2.如果线性表长度大于等于数组长度,抛出异常或者动态增加数组容量

3.从最后一个元素开始向前遍历到待插入位置,分别将他们向后移动一个位置

4.插入元素,线性表长+1

//两种方式插入
​
1.
//插入元素-顺序插入
void insert_ArrayTail(int key, Array* array)
{
    //数组还有容量才能进行插入
    if (array->length < array->Size)
    {
        array->data[array->length] = key;
        array->length++;
    }
    else
    {
        printf("插入错误:数组容量不足!\n");
    }
}
​
2.
//插入元素-中间位置插入
void insert_ArrayIndex(int key, int index, Array* array)
{
    //判断插入位置是否正确
    if (index<0 || index>array->length)
    {
        printf("插入错误:插入位置错误!\n");
        return;
    }
    //记录当前数组指向位置
    int pos = array->length;
    //数组还有容量
    if (array->length < array->Size)
    {
        while (index<pos)
        {
            //依次向后移动一位
            array->data[pos] = array->data[pos - 1];
            pos--;
        }
        //插入元素
        array->data[index] = key;
        //数组长度+1
        array->length++;
    }
    else
    {
        printf("插入错误:数组容量不足!\n");
    }
}

6.删除操作

思路:

1.如果删除位置不合理,则抛出异常

2.从删除位置开始遍历到最后一个元素位置,分别将他们向前移动一个位置

3.表长-1

//两种方式
​
1.
//删除元素-根据元素来删除
void delete_ArrayElement(int delete_key, Array* array)
{
    //先找到元素下标
    int delete_index = find_Array(delete_key,array);
    //判断下标是否正确
    if (delete_index == -1)
    {
        printf("删除错误:数组中没有这个元素!\n");
        return;
    }
    //元素依次向前移动,覆盖掉待删除元素
    while (delete_index < array->length)
    {
        //元素向前覆盖
        array->data[delete_index] = array->data[delete_index + 1];
        delete_index++;
    }
    //数组元素-1
    array->length--;
}
​
2.
//删除元素-根据下标来删除
void delete_ArrayIndex(int index, Array* array)
{
    //判断下标是否合理
    if (index<0 || index>=array->length)
    {
        printf("删除错误:下标错误!\n");
        return;
    }
    //元素依次向前移动,覆盖掉待删除元素
    while (index < array->length)
    {
        //元素向前覆盖
        array->data[index] = array->data[index + 1];
        index++;
    }
    //数组元素-1
    array->length--;
​
}

7.改动操作

//两种方式
1.
//改变元素-根据下标来改动
void change_ArrayIndex(int change_key,int index, Array* array)
{
    //判断下标是否合理
    if (index < 0 || index >= array->length)
    {
        printf("改动错误:下标错误!\n");
        return;
    }
    array->data[index] = change_key;
}
​
2.     
//改变元素-根据元素来改动
void change_ArrayElement(int key, int change_key, Array* array)
{
    //先找到元素下标
    int change_pos = find_Array(key, array);
    //判断下标是否正确
    if (change_pos == -1)
    {
        printf("改动错误:数组中没有这个元素!\n");
        return;
    }
    array->data[change_pos] = change_key;
}

8.优缺点

线性表的顺序存储结构,在读数据时 (查找元素-根据下标返回元素值或者改动元素),时间复杂度是O(1),而插入和删除的时候,时间复杂度都是O(n)。

所以线性表的顺序存储结构比较元素个数不太变化,而更多的是存取数据的应用

优点

1.无须为表示元素之间的逻辑关系而增加额外的存储空间

2.可以快速存取表中任一位置的元素

缺点

1.插入和删除操作需要移动大量的元素-耗费时间

2.当线性表长度变化较大时,难以确定存储空间的容量

3.造成存储空间的“碎片”

三、线性表的链式存储结构(单链表)

1.定义

1.特点:用一组任意的存储单元存储线性表的数据元素,这组存储单元可以是连续的,也可以是不连续的。所以这些数据元素可以存在内存未被占用的任意位置

链式结构中需要存储它的后继元素的存储地址

2.存储数据元素信息的域叫:数据域

3.存储直接后继位置的域叫:指针域

4.这两部分组成数据元素List的存户映像,称为结点

5.n个结点链结成一个链表,即为线性表的链式存储结构,每个结点只包含一个指针域,所以叫单链表

6.首元结点:单链表的第一个结点

7.头指针:链表第一个结点的存储位置

8.头结点:首元结点前附设的一个结点

2.头指针和头结点的异同

头指针

1.是指向链表第一个结点的指针。如果链表有头结点,则是指向头结点的指针

2.头指针具有标志作用,所以常用头指针冠以丽娜表的名字

3.无论链表是否为空,头指针均不为空。头指针是链表的必要元素(也只有通过头指针才能找到后续结点)

头结点

1.是为了链表操作的统一和方便而设立的。放在第一元素之前,其数据域一般无意义

2.有了头结点,对在首元结点前插入结点和删除首元结点,其操作才与其他结点的操作统一

3.头结点不一定是链表必需的元素

3.单链表的创建和增删改查

#include<stdio.h>
#include<stdlib.h>
#include<time.h>
​
​
//定义链表结点
typedef struct NodeList
{
    int element;//数据域
    struct NodeList* pNext;//指针域
}Node;
​
//初始化头结点
Node* init_head();
​
//插入元素-随机n个元素的值-头插法
void allinit_List_head(int n, Node* node);
// 插入元素 - 随机n个元素的值 - 尾插法
void allinit_List_tail(int n, Node* node);
//插入元素-头插法
void insert_ListHead(int key,Node* node);
//插入元素-尾插法
void insert_ListTail(int key, Node* node);
//插入元素-中间插入
void insert_ListIndex(int key,int index, Node* node);
//删除元素-根据元素来删除
void delete_ListElement(int delete_key,Node* node);
//删除元素-根据元素位置来删除
void delete_ListIndex(int index, Node* node);
//删除元素-整表删除
void delete_List(Node* node);
//改变元素-根据位置来改
void change_List(int change_key, int index, Node* node);
//查找元素-返回元素位置(将首元结点位置定义为1)(也可以返回结点指针)
int find_List(int find_key ,Node* node);
//打印链表
void print(Node* node);
​
​
int main()
{
    //初始化
    Node* mylist = init_head();
    //头插
    insert_ListHead(2, mylist);
    //尾插
    insert_ListTail(4, mylist);
    insert_ListHead(1, mylist);
    //中间插入
    insert_ListIndex(3, 3, mylist);
    //整表插入
    //allinit_List_head(0, mylist);
    delete_List(mylist);
    
    print(mylist);
​
​
    return 0;
}
​
//初始化头结点
Node* init_head()
{
    //定义一个头结点
    Node* head = (Node*)malloc(sizeof(Node));
    //判断初始化是否成功
    if (head)//成功
    {
        //给头结点中的指针域一个指向
        head->pNext = NULL;
        //表示链表元素个数
        head->element = 0;
    }
    else//失败
    {
        printf("分配内存失败!");
        exit(0);
    }
    return head;
}
​
//插入元素-随机n个元素的值
void allinit_List_head(int n, Node* node)
{
    if (n < 1)
    {
        printf("插入错误:元素个数不能小于1!\n");
        return;
    }
    //生成随机数种子
    srand((unsigned int)time(NULL));
    for (int i = 0; i < n; i++)
    {
        //初始化新结点
        Node* temp = (Node*)malloc(sizeof(Node));
        //给数据域中的数据赋值
        temp->element = rand() % 100 + 1;
​
        //让这个新的结点变成首元结点
        temp->pNext = node->pNext;
        node->pNext = temp;
        //链表元素+1
        node->element++;
    }
​
}
​
// 插入元素 - 随机n个元素的值 - 尾插法
void allinit_List_tail(int n, Node* node)
{
    if (n < 1)
    {
        printf("插入错误:不能小于1!\n");
        return;
    }
    //生成随机数种子
    srand((unsigned int)time(NULL));
    //临时指针,用来遍历链表
    Node* pcurrent = node;
    //遍历链表,到最后一个结点
    while (pcurrent->pNext)
    {
        pcurrent = pcurrent->pNext;
    }
    //进行插入
    for (int i = 0; i < n; i++)
    {
        //初始化新结点
        Node* temp = (Node*)malloc(sizeof(Node));
        //给数据域中的数据赋值
        temp->element = rand() % 100 + 1;
​
        //让这个新的结点变成最后一个结点
        pcurrent->pNext = temp;
        temp->pNext = NULL;
        //移动临时指针
        pcurrent = temp;
        //链表元素+1
        node->element++;
    }
}
​
//插入元素-头插法
void insert_ListHead(int key, Node* node)
{
    //定义一个待插入的结点
    Node* temp = (Node*)malloc(sizeof(Node));
    //插入元素
    temp->element = key;
    //指向链表首元结点
    temp->pNext = node->pNext;
    //头结点指向temp
    node->pNext = temp;
    //链表元素个数加1
    node->element++;
}
​
//插入元素-尾插法
void insert_ListTail(int key, Node* node)
{
    //创建待插入结点
    Node* temp = (Node*)malloc(sizeof(Node));
    //插入元素
    temp->element = key;
    //临时指针,用来遍历链表,找到插入位置(也可以定义一个尾结点)
    Node* pcurrent = node;
    //遍历链表
    while (pcurrent->pNext)
    {
        pcurrent = pcurrent->pNext;
    }
    //此时的pcurrent就是最后一个结点
    //让其指向待插入结点
    pcurrent->pNext = temp;
    //让temp变成最后一个结点
    temp->pNext = NULL;
    //链表元素个数加1
    node->element++;
}
​
//中间位置插入
void insert_ListIndex(int key,int index, Node* node)
{
    //判断插入位置是否正确
    if (index<1 || index>node->element)
    {
        printf("插入错误:插入位置错误!\n");
        return;
    }
    //定义一个待插入结点
    Node* temp = (Node*)malloc(sizeof(Node));
    temp->element = key;
    //记录结点位置
    int pos = 1;
    //找到待插入位置
    //临时指针,用来遍历链表
    Node* pcurrent = node;
    //遍历链表
    while (pcurrent->pNext)
    {
        //找到下标,进行插入
        if (index == pos)
        {
            //让待插入结点指向原来位置的结点
            temp->pNext = pcurrent->pNext;
            pcurrent->pNext = temp;
            //链表元素+1
            node->element++;
            return;
        }
        //未找到下标
        pcurrent = pcurrent->pNext;
        pos++;
    }
​
}
​
//删除元素 - 根据元素来删除
void delete_ListElement(int delete_key, Node* node)
{
    //首先要删除的元素位置
    int delete_pos = find_List(delete_key, node);
    if (delete_pos == -1)
    {
        printf("删除错误:没有这个元素!\n");
        return;
    }
    //记录待删除元素的前一个位置
    int front_pos = 0;
    //临时指针,用来遍历链表
    Node* pcurret = node;
    //遍历链表
    while (pcurret->pNext)
    {
        //找到结点位置
        if (front_pos == delete_pos - 1)
        {
            //找到被删除的结点
            Node* free_node = pcurret->pNext;
            pcurret->pNext = pcurret->pNext->pNext;
            //释放被删除的结点
            free(free_node);
            //链表元素-1
            node->element--;
            return;
        }
        //未找到
        pcurret = pcurret->pNext;
        front_pos++;
    }
}
​
//删除元素-根据元素位置来删除
void delete_ListIndex(int index, Node* node)
{
    //判断元素位置是否正确
    if (index<1 || index>node->element)
    {
        printf("删除错误:删除元素位置错误!\n");
        return;
    }
    //记录待删除元素的前一个位置
    int front_pos = 0;
    //临时指针,用来遍历链表
    Node* pcurret = node;
    //遍历链表
    while (pcurret->pNext)
    {
        //找到结点位置
        if (front_pos == index - 1)
        {
            //找到被删除的结点
            Node* free_node = pcurret->pNext;
            pcurret->pNext = pcurret->pNext->pNext;
            //释放被删除的结点
            free(free_node);
            //链表元素-1
            node->element--;
            return;
        }
        //未找到
        pcurret = pcurret->pNext;
        front_pos++;
    }
}
​
//删除元素-整表删除
void delete_List(Node* node)
{
    //定义两个临时指针,pcurrent用来移动指针位置,free_node用来删除结点
    Node* pcurrent = node->pNext;
    Node* free_node;
    while (pcurrent)
    {
        free_node = pcurrent;
        pcurrent = pcurrent->pNext;
        free(free_node);
        //头指针是不会被删除的,元素-1
        node->element--;
    }
    //头结点指针域为空
    node->pNext = NULL;
}
​
//改变元素-根据位置来改
void change_List(int change_key, int index, Node* node)
{
    //判断要改动位置是否正确
    if (index<1 || index>node->element)
    {
        printf("改动错误:位置错误!\n");
        return;
    }
    //记录元素位置
    int change_pos = 1;
    //临时指针,用来遍历链表
    Node* pcurrent = node;
    //遍历链表
    while (pcurrent->pNext)
    {
        //找到待改变元素位置
        if (change_pos == index)
        {
            pcurrent->pNext->element = change_key;
            return;
        }
        //未找到
        pcurrent = pcurrent->pNext;
        change_pos++;
    }
}
​
//查找元素-返回元素位置(将首元结点位置定义为1)(也可以返回结点指针)
int find_List(int find_key,Node* node)
{
    //临时指针,用来遍历链表
    Node* pcurrent = node->pNext;
    //表示元素位置
    int pos = 1;
    //遍历链表进行查找
    while (pcurrent)
    {
        if (pcurrent->element == find_key)
        {
            return pos;
        }
        pcurrent = pcurrent->pNext;
        //元素位置向后移动
        pos++;
    }
    return -1;
}
​
//打印链表
void print(Node* node)
{
    //临时指针,用来遍历链表
    Node* pcurrent = node;
    //记录元素位置
    int pos = 1;
    //链表元素个数
    printf("一共 %d 个元素!\n", node->element);
    //遍历链表,进行打印
    while (pcurrent->pNext)
    {
        printf("第 %d 个元素是:%d\n", pos, pcurrent->pNext->element);
        pcurrent = pcurrent->pNext;
        pos++;
    }
}
​

四、对线性表的选择

1.如果线性表需要频繁的查找,很少进行插入和删除操作时,宜采用顺序存储结构

2.如果需要经常插入和删除时,应该采用单链表结构

3.当线性表中的元素个数变化较大或者根本不知道有多大的时候,最好采用单链表结构。这样不用考虑存储空间大小的问题

4.线性表的顺序存储结构和链式存储结构各有优缺点,使用时应该根据实际情况选择能满足需求并且性能更高的结构

比如:

游戏开发中,用户的个人信息,除了注册的时候要插入数据外,更多的情况是读取数据,可以考虑采用顺序存储结构

而对玩家的武器装备列表来说,会经常增加装备,或者丢弃装备,这个时候可以考虑采用链式存储结构。不过这只是简单的比喻,现实中考虑的问题会更加复杂

五、其他链式存储结构

1.静态链表:是给没有指针的高级语言设计的一种实现单链表能力的方法

2.循环链表:将单链表中最后一个结点的指针域由空指针改为指向头结点,这样就使整个单链表形成了一个闭环

解决了:从某一个结点出发,访问到链表的全部结点

3.双向链表:是在单链表的每一个结点的指针域中,再设置一个指向其前驱结点的指针域(用空间换时间)

0

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值