目录
线性表的定义:由n个类型相同的数据元素组成的有限有序序列
常见的线性表:顺序表、链表、栈、队列
线性表在逻辑上是线性结构,但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组或者链式结构的形式存储。
逻辑结构:指数据对象中数据元素之间的相互关系
物理结构:指数据的逻辑结构在计算机中的存储形式
一、顺序表
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删改查。
顺序表存储结构的代码:
#define MAXSIZE 10 //存储空间初始分配量
typedef int ElemType; //ElemType类型根据情况而定
typedef struct
{
ElemType data[MAXSIZE]; //数组存储顺序表的数据元素
int length; //顺序表当前长度
}SqList;
顺序表的一些操作
(1)获得元素操作
如过我们要获得顺序表中第 i 个位置的元素,就是把数组中下标为 i-1 的数据元素返回
void GetElem(SqList* ps, size_t i,ElemType* e)//获得元素操作,返回顺序表中下标为i的元素给e
{
if (ps->length == 0 || i<1 || i>ps->length)//如果下标不在顺序表的有效范围内返回错误
{
printf("error");
}
*e = ps->data[i - 1];
}
(2)插入元素操作
在顺序表的第i个位置插入数据元素e,插入元素算法的思路:
- 如果插入位置不合理,则抛出异常
- 如果线性表当前长度大于顺序表最大长度,则抛出异常或者动态增容
- 从最后一个元素开始向前遍历到第 i 个位置,分别都将他们向后移动一位
- 将要插入的元素填入i的位置
- 表长加1
插入元素代码实现:
void ListInsert(SqList* ps, size_t i, ElemType e)
{
int k = 0;
if (ps->length == MAXSIZE) //顺序表已满
{
printf("error");
}
if (i<1 || i>ps->length + 1) //i不在范围内
{
printf("error");
}
if (i <= ps->length) //若插入的元素不在表尾
{
for (k = ps->length-1; k >= i-1; k-- )//将要插入元素位置后数据元素向后移动一位
{
ps->data[k + 1] = ps->data[k];
}
}
ps->data[i-1] = e; //将新元素插入
ps->length++;
}
(3)删除元素操作
删除顺序表的第i个数据元素,并用e返回其值,删除元素算法的思路:
- 如果删除位置不正确,则抛出异常
- 取出要删除的元素,用e返回
- 从删除元素开始位置遍历到最后一个元素位置,分别将他们向前移动一位
- 表长减1
删除元素代码实现:
void ListDelet(SqList* ps, size_t i, ElemType* e) //删除顺序表的第i个数据元素,并用e返回其值
{
int k = 0;
if (ps->length == 0) //顺序表为空
{
printf("error");
}
if (i<1 || i>ps->length) //删除位置不正确
{
printf("error");
}
*e = ps->data[i - 1];
if (i < ps->length) //若删除不是最后
{
for (k = i; k <ps->length; k++)
{
ps->data[k-1] = ps->data[k]; //将删除位置后边的元素前移
}
}
ps->length--;
}
顺序表在插入和删除数据时,需要移动大量元素,其平均时间复杂度为O(N),但是它能快速存取表中的任意位置的元素。基于顺序表插入和删除需要大量移动元素的缺点,引出一种线性表的链式存储结构——链表
二、链表
链表是用任意的存储单元存储线性表的数据元素,这些存储单元可以是连续的,也可以是不连续的,也就是说,这些数据元素可以在内存未被占用的任意位置。
为了表示每个数据元素
和它直接后继的数据元素
的关系,
不仅要存储数据本身(数据域),还需要存储指向直接后继元素的指针(指针域),我们把这两部分称为结点。
每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。链表中第一个结点的存储位置叫做头指针,而链表的最后一个结点指针为NULL
n个结点链结成一个链表,因为此链表的每个结点中只包含一个指针域,所以叫单链表
链表存储结构的代码:
typedef int ElemType; //ElemType类型根据情况而定
typedef struct Node
{
ElemType data;
struct Node* next;
}Node;
链表的一些操作:
(1)单链表的读取操作:
获得链表第i个数据
- 声明一个指针p指向链表的第一个结点,初始化j从1开始
- 当j<i时,就遍历链表,让p的指针向后移动,不断指向下一个结点,j累加1
- 若到链表末尾,p为NULL,则说明第i个结点不存在
- 否则查找成功,返回结点p的数据
void GetElem(Node* L, int i, ElemType* e)
{
Node* p;
p = L->next; //让p指向链表L的第一个结点
int j = 1; //j为计数器
while (p && j < i) //p不为NULL并且j还没有等于i的时候,循环继续
{
p = p->next;
++j;
}
if (!p || j > i) //第i个结点不存在
{
printf("error");
}
*e = p->data; //取第i个节点的数据
}
(2)单链表的插入
假如有一个结点p,它指向的下一个结点为p->next,现在要在中间插入结点s。插入操作的就是让p的后继结点p->next变为s的后继结点,再把结点s变为p的后继结点
s->next = p->next;
p->next = s;
注意这两个的前后关系不能搞错,否则会导致插入失败
void ListInsert(Node** L, int i, ElemType e) //在链表第i个结点位置插入元素e
{
Node* p;
p = *L;
int j = 1;
while (p && j < i)
{
p = p->next;
++j;
}
if (!p || j > i)
{
printf("error");
}
Node* s = (Node*)malloc(sizeof(Node)); //生成新的结点s
s->data = e;
s->next = p->next;
p->next = s;
}
(3)单链表的删除
假如有一个结点p,p的后继结点q,q的后继结点q->next,现在要删除q结点。删除操作就是把p的后继结点改为p的后继的后继结点
q = p->next;
p->next = q->next;
void ListDelete(Node** L, int i, ElemType* e) //删除L的第i个结点,并用e返回其值
{
Node* p;
p = *L;
int j = 1;
while (p->next && j < i)
{
p = p->next;
++j;
}
if (!(p->next) || j > i)
{
printf("error");
}
Node* q;
q = p->next;
p->next = q->next;
*e = q->data;
free(q); //让系统回收此结点,释放内存
}
链表与顺序表的比较
相比于顺序表结构,由于不必须按顺序存储,链表在插入和删除的时候可以达到O(1)的复杂度,比顺序表表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而顺序表相应的时间复杂度可以是O(1)
(4)单链表的整表创建
单链是一种动态结构,所以创建单链表的过程,就是一个动态生成链表的过程,即从“空表”的初始状态起,以此建立各个元素结点,并逐个插入链表
单链表创建的算法思路:
- 声明指针p和计数器变量i
- 初始化一个空链表L
- 让L的头结点的指针指向NULL,建立一个带头结点的单链表
- 循环
生成新结点赋值给p
随机生成一数字复制给p的数据域
让p插入到头结点与新节点之间
这种创建单链表的方法就做头插法
void CreateListHead(Node** L,int n) //建立有n个元素的带有头结点的单链表
{
srand(time(0)); //初始化随机数种子
Node* p;
*L = (Node*)malloc(sizeof(Node));
(*L)->next = NULL; //先建立一个带头结点的单链表
int i = 0;
for (i = 0; i < n; i++)
{
p= (Node*)malloc(sizeof(Node)); //生成新结点
p->data = rand() % 100 + 1; //随机生成1~100之间的数字
p->next = (*L)->next;
(*L)->next = p; //插入到表头
}
}
还有一种创建链表的方法:尾插法
void CreateListTail(Node** L, int n)
{
srand(time(0));
Node* p;
*L = (Node*)malloc(sizeof(Node));
Node* r;
r = *L; //r为指向尾部的结点
int i = 0;
for (i = 0; i < n; i++)
{
p = (Node*)malloc(sizeof(Node));
p->data = rand() % 100 + 1;
r->next = p; //将表尾终端结点的指针指向新结点
r = p; //将当前的新结点定义为表尾终端结点
}
r->next = NULL; //表示当前链表结束
}
(5)单链表的整表删除
当我们打算不使用这个链表的时候,就需要把它销毁,单链表整表删除的算法思想:
- 声明结点p和q
- 将第一个结点赋给p
- 循环
将下一个结点赋值给q
释放p
将q赋给p
void ClearList(Node** L)
{
Node* p, *q;
p = (*L)->next;
while (p) //没到表尾循环继续
{
q = p->next;
free(p);
p = q;
}
(*L)->next = NULL; //头结点指针域置空
}