线性表
顺序表和链表
抽象数据结构-数据结合和操作集合
头结点:不存放数据元素的第一个结点
首元结点:存放数据元素的第一个结点
一、线性表的定义
零个或多个数据元素的有限序列
元素之间是有顺序的,如果存在多个元素,则第一个元素无前驱,最后一个元素无后继。其他每一个元素都有且只有一个前驱和一个后继。
在较复杂的线性表中,一个数据元素可以由若干个数据项组成
二、线性表的顺序存储结构
用一段地址连续的存储单元依次存储线性表的数据元素-物理地址连续
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
本文详细介绍了线性表的两种主要存储结构——顺序存储和链式存储。顺序存储利用一维数组实现,查找效率高但插入删除操作需要移动元素;链式存储则以结点形式存储,插入删除灵活但需要额外的指针存储空间。文章还对比了两者的优缺点,并给出了C语言实现的示例代码,帮助理解这两种数据结构的操作。
3566

被折叠的 条评论
为什么被折叠?



