1、概念
详见https://blog.csdn.net/qq_30611601/article/details/79516986
线性表是最基本、最简单、也是最常用的一种数据结构。线性表是数据结构的一种,一个线性表是n个具有相同特性的数据元素的有限序列。线性表中数据元素之间的关系是一对一的关系,即除了第一个和最后一个数据元素之外,其它数据元素都是首尾相接的**(注意,这句话适用大部分线性表,而不是全部)**
2、线性表常用操作
线性表的操作包括如下几种
(1) InitList(&L) 初始化,构造一个空的线性表
(2) ListEmpty(L) 判断线性表是否为空,true or flase
(3) ClearList(&L) 清空线性表中的内容
(4) GetElem(L,i,&e) 返回线性表i位置上的元素值,通过e返回
(5) LocateElem(L,e) 在线性表中找到与e相同的元素,成功则返回其序号,否则返回0表示失败
(9) Listinsert(&L,i,e) 如果线性表存在了,而且i符合条件,则在i位置插入一个元素e
(10)ListDelete(*L,i,*e) 删除i位置上的元素,并用e返回其值
(5) ListLength(L) 返回线性表的长度
3、顺序存储结构线性表
3.1、线性表的顺序存储结构
1)顺序表示,其实就是数组,连续内存空间
2)优点:
随机访问特性,查找O(1)时间,存储密度高;
逻辑上相邻的元素,物理上也相邻;
无须为表中元素之间的逻辑关系而增加额外的存储空间;
3)缺点:
插入和删除需移动大量元素;
当线性表长度变化较大时,难以确定存储空间的容量;
造成存储空间的“碎片
#define MAXSIZE 20 //存储空间初始分配量
typedef int Elemtype; //Elemtype数据类型根据实时而定,这里假设为int
typedef int Status;
struct List {
/* data: 元素数据存储空间
length: 线性表长度(变动的),从1开始
MAXSIZE: 线性表最大长度,即数组长度(不变) */
Elemtype data[MAXSIZE];
int length;
};
3.2、顺序结构的获取元素操作
1)当为空线性表时,ERROR抛出
2)返回位置异常时,ERROR抛出
//返回线性表i位置上的元素值,通过e返回
Status GetElem(List L, int i, Elemtype *e)
{
if (L.length == 0 || i<1 || i>L.length)
return ERROR;
*e = L.data[i - 1];
///*为什么是i-1,这是由于我们平时以及线性表都是下标从1开
//始的,而在C++中数组索引是从0开始,所以我们要对应减1
return OK;
}
5、顺序结构的插入操作
1)保证线表不是空表
2)插入位置不合理,抛出异常
3)表的长度达到最大容量,插不进
4)特殊情况处理:直接插入表尾,与非表尾情况
5)插入数据后,后面的数据要往后移
【注意:该插入时将值插入到 i 之前】
//插值到线性表操作
Status Listinsert(List *L, int i, Elemtype e) //线性表,插入表位置,插入元素值e
{
/*插入元素到第I位置之前*/
int k;
if (L->length == MAXSIZE) //表满,插不进
return ERROR;
if (i<1 || i>L->length + 1) //插入位置不合理
return ERROR;
if (i <= L->length) //插入位置不为表尾
{
for (k = L->length - 1; k >= i - 1; k--)
L->data[k+1] = L->data[k];
}
L->data[i-1] = e; //插入目标值
L->length++; //更新线性表长度
return OK; //完成标志
}
3.3、顺序结构的删除操作
1)删除位置不合理,抛出异常
2)考虑删除位置不是最后位置与是最后位置情况
3)删除后,后面数据前移动1个
//删除
Status ListDelete(List *L, int i, Elemtype *e) //表,位置,删除元素值
{
if (L->length == 0) //空表,异常抛出
return ERROR;
if (i > L->length || i < 1) //删除位置不合理,异常抛出
return ERROR;
*e = L->data[i - 1]; //删除目标元素
if (i < L->length) //删除位置非表尾
{
for (int k = i; k < L->length ; k++)
{
L->data[k - 1] = L->data[k];
}
}
L->length--;
return OK;
}
3.4、顺序结构线性表完整代码
/*
作者:kimicr
时间:20200406
功能:c++实现之顺序结构线性表
版本:VS2013
*/
#include"iostream"
#include"stdlib.h"
#include"stdio.h"
#include"time.h"
#include"List.h"
using namespace std;
#define ERROR 0
#define OK 1
/*
线性表的操作包括如下几种
(1) InitList(*L) 初始化,构造一个空的线性表
(2) ListEmpty(L) 判断线性表是否为空,true or flase
(3) ClearList(*L) 清空线性表中的内容
(4) GetElem(L,i,*e) 返回线性表i位置上的元素值,通过e返回
(5) LocateElem(L,e) 在线性表中找到与e相同的元素,成功则返回其序号,否则返回0表示失败
(6) Listinsert(*L,i,e) 如果线性表存在了,而且i符合条件,则在i位置插入一个元素e
(7) ListDelete(*L,i,*e) 删除i位置上的元素,并用e返回其值
(8) ListLength(L) 返回线性表的长度
*/
//顺序存储线性表
#define MAXSIZE 20 //存储空间初始分配量
typedef int Elemtype; //Elemtype数据类型根据实时而定,这里假设为int
typedef int Status;
struct List {
/* data: 元素数据存储空间
length: 线性表长度(变动的),从1开始
MAXSIZE: 线性表最大长度,即数组长度(不变) */
Elemtype data[MAXSIZE];
int length;
};
//返回线性表i位置上的元素值,通过e返回
Status GetElem(List L, int i, Elemtype *e)
{
if (L.length == 0 || i<1 || i>L.length)
return ERROR;
*e = L.data[i - 1];
///*为什么是i-1,这是由于我们平时以及线性表都是下标从1开
//始的,而在C++中数组索引是从0开始,所以我们要对应减1
return OK;
}
//插值到线性表操作
Status Listinsert(List *L, int i, Elemtype e) //线性表,插入表位置,插入元素值e
{
/*插入元素到第I位置之前*/
int k;
if (L->length == MAXSIZE) //表满,插不进
return ERROR;
if (i<1 || i>L->length + 1) //插入位置不合理
return ERROR;
if (i <= L->length) //插入位置不为表尾
{
for (k = L->length - 1; k >= i - 1; k--)
L->data[k+1] = L->data[k];
}
L->data[i-1] = e; //插入目标值
L->length++; //更新线性表长度
return OK; //完成标志
}
//删除
Status ListDelete(List *L, int i, Elemtype *e) //表,位置,删除元素值
{
if (L->length == 0) //空表,异常抛出
return ERROR;
if (i > L->length || i < 1) //删除位置不合理,异常抛出
return ERROR;
*e = L->data[i - 1]; //删除目标元素
if (i < L->length) //删除位置非表尾
{
for (int k = i; k < L->length ; k++)
{
L->data[k - 1] = L->data[k];
}
}
L->length--;
return OK;
}
int main()
{
List list1;
list1.data[0] = 50;
list1.length = 1; //初始化线性表
Listinsert(&list1, 1, 1);
Listinsert(&list1, 1, 2);
Listinsert(&list1, 1, 3);
//遍历并输出该链表上的所有数据
cout << "遍历并输出该链表上的所有数据:" << endl;
for (int i = 0; i <list1.length ; i++)
cout << list1.data[i] << " ";
cout << endl;
int e=0;
ListDelete(&list1, 2,&e); //删除元素
//遍历并输出该链表上的所有数据
cout << "遍历并输出该链表上的所有数据:" << endl;
for (int i = 0; i < list1.length; i++)
cout << list1.data[i] << " ";
cout << endl;
system("pause");
return 0;
}
遍历并输出该链表上的所有数据:
3 2 1 50
遍历并输出该链表上的所有数据:
3 1 50
请按任意键继续. . .
4、链式结构线性表
4.1、链式结构
1)链式存储结构,分散的内存空间
2)优点
插入、删除不需移动其他元素,只需改变指针.
链表各个节点在内存中空间不要求连续,空间利用率高
元素理论上可以无限个,不需要预分配内存
3)缺点
查找需要遍历操作,比较麻烦
struct Node
{
Elemtype data; //存储数据
Node *next; //存储下一个结点的地址
};
typedef Node *LinkList; //定义指针数据类型Node
4.2、单链表的读取(查找)
1)声明一个指向链表的指针P,j=1开始计数
2)j<i,不断遍历,让指针p后移
3)当p指向为空时,即链表找完时,则表明第i元素不存在
4)否则查找成功
//取值
Status GetElem(LinkList L, int i, Elemtype *e)
{
int j;
LinkList p; //声明一个指向链表结构的指针
p = L->next; //初始化指针,指向链表L的第一个节点
j = 1; //j为计数器
while ( p&& j < i) //*p不为空以及j还没有大于等于i时,继续循环
{
p = p->next; //指针指向下一节点
++j; //计数器加一
}
if (!p || j>i) //第i元素不存在时,抛出异常
return ERROR;
*e = p->data; //返回第i值
return OK;
}
4.3、单链表的插入
1)需要插入目标链表,位置,数值
2)创建两个指针,分别指向链表与新元素地址
3)j=1遍历计数,找到插入位置,如p=NULL表示链表中没有,反之则分配空间生成一个新的节点(new实现)
4)赋值以及更改i-1,与新节点的指针域值
//插入第i位置之前
Status Listinsert(LinkList *L, int i, Elemtype e)
{
int j; //计数器
LinkList p;
p = *L; //指向链表
j = 1; //计数器
while (p&&j < i) //遍历找到插入新节点的目标位置
{
p = p->next;
++j;
}
if (!p || j>i)
return ERROR; //链表中不存在第i个元素
Node *s = new Node; //生成的新节点
s->data = e; //更新节点的数值data
s->next = p->next; //将p指向的下一节作为给s节点的下一节点
p->next = s; //将s地址作为p的下一节点
//注意:这里不要释放s,因为是插入节点
return OK;
}
4.4、单链表的删除
1)需要目标链表,删除位置
2)建立两个指针p,q,分别用来指向链表与要删除节点地址
3)遍历,如P=null,没有这个元素,否者将该元素地址
4)完成删除,并回收节点空间delete q;
4.5 、单链表的整表创建
1)声明节点指针P以及计数器count
2)初始化一空链表L
3)让L头指针指向Null,建立一个带头节点的单链表
4)循环,分配空间生成新节点
5)完成创建
【注意:头插法,即使让新的节点总是在第一位】
//单链表的整表创建
void CreateListHead(LinkList *L, int n)
{
/*随机产生n个元素值,建立代表头节点的单链线性表L*/
/*插队方式,始终让新产生节点处于第1节点【头插法】*/
LinkList p;
srand(time(0)); //初始化随机数种子
*L = new Node;
(*L)->next = NULL; //先建立一个带头节点的单链表
for (int i = 0; i < n; i++)
{
p = new Node; //生成新节
p->data = rand() % 100 + 1; //随机生成100以内的数
p->next = (*L)->next;
(*L)->next = p; //插入到表头
}
}
【还有尾插法:即使将每次新产生的节点总是处于最后一位】
*插队方式,始终让新产生节点处于末尾【尾插法】*/
void CreateListTail(LinkList *L, int n)
{
LinkList p,r;
srand(time(0)); //初始化随机数种子
*L = new Node;
r = *L;
for (int i = 0; i < n; i++)
{
Node *p = new Node; //生成新节
p->data = rand() % 100 + 1; //随机生成100以内的数
r->next = p; //插入到表头
r = p;
}
r->next = NULL;
}
4.7、单链表的整表删除
1)需两个节点指针P,Q,作用分别是指向下一节点以及释放当前节点
Status ClearList(LinkList *L)
{
/*初始条件,线性表已经存在 、目标:清空线性表*/
LinkList p, q;
p = (*L)->next;
while (p)
{
q = p->next;
delete p;
p = q;
}
(*L)->next = NULL; //头节点指针域为空
return OK;
}
【链表 VS 顺序存储结构线性表】
4.8 链表完全代码
【注意:LinkLink *L; 是一个二重指针】
/*
作者:kimicr
时间:20200406
功能:c++实现之顺序结构线性表
版本:VS2013
*/
typedef int Elemtype;
typedef int Status;
struct Node
{
Elemtype data; //存储数据
Node *next; //存储下一个结点的地址
};
typedef Node *LinkList; //定义新的数据类型-指向结构体的指针
//单链表的整表创建
//LinkLink *L;是一个二重指针
void CreateListHead(LinkList *L, int n)
{
/*随机产生n个元素值,建立代表头节点的单链线性表L*/
/*插队方式,始终让新产生节点处于第1节点【头插法】*/
LinkList p;
srand(time(0)); //初始化随机数种子
*L = new Node;
(*L)->next = NULL; //先建立一个带头节点的单链表
for (int i = 0; i < n; i++)
{
p = new Node; //生成新节
p->data = rand() % 100 + 1; //随机生成100以内的数
p->next = (*L)->next;
(*L)->next = p; //插入到表头
}
}
/*插队方式,始终让新产生节点处于末尾【尾插法】*/
void CreateListTail(LinkList *L, int n)
{
LinkList p,r;
srand(time(0)); //初始化随机数种子
*L = new Node;
r = *L;
for (int i = 0; i < n; i++)
{
Node *p = new Node; //生成新节
p->data = rand() % 100 + 1; //随机生成100以内的数
r->next = p; //插入到表头
r = p;
}
r->next = NULL;
}
//取值
Status GetElem(LinkList L, int i, Elemtype *e)
{
int j;
LinkList p; //声明一个指向链表结构的指针
p = L->next; //初始化指针,指向链表L的第一个节点
j = 1; //j为计数器
while ( p&& j < i) //*p不为空以及j还没有大于等于i时,继续循环
{
p = p->next; //指针指向下一节点
++j; //计数器加一
}
if (!p || j>i) //第i元素不存在时,抛出异常
return ERROR;
*e = p->data; //返回第i值
return OK;
}
//插入第i位置之前
Status Listinsert(LinkList *L, int i, Elemtype e)
{
int j; //计数器
LinkList p;
p = *L; //指向链表
j = 1; //计数器
while (p&&j < i) //遍历找到插入新节点的目标位置
{
p = p->next;
++j;
}
if (!p || j>i)
return ERROR; //链表中不存在第i个元素
Node *s = new Node; //生成的新节点
s->data = e; //更新节点的数值data
s->next = p->next; //将p指向的下一节作为给s节点的下一节点
p->next = s; //将s地址作为p的下一节点
//注意:这里不要释放s,因为是插入节点
return OK;
}
//删除
Status ListDelete(LinkList *L, int i, int *e)
{
int j=1;
LinkList p,q;
p = *L;
while (p->next&&j < i) //遍历寻找第i元素,当j=i时便会跳出循环,此时p->next是第i-1节点地址。p此时指向的时i-1节点
{
p = p->next;
++j;
}
if (!p || j>i)
return ERROR; //第i元素不存在
q = p->next; //现在将p后继节点地址赋给q,让q来指向第i节点
p->next = q->next; //将q指向i节点中的后继节点(i+1)地址赋给p的指针域
*e = p->data;
delete q; //让系统回收一个Node节点空间,释放内存
return OK;
}
Status ClearList(LinkList *L)
{
/*初始条件,线性表已经存在 、目标:清空线性表*/
LinkList p, q;
p = (*L)->next;
while (p)
{
q = p->next;
delete p;
p = q;
}
(*L)->next = NULL; //头节点指针域为空
return OK;
}
int main()
{
LinkList list2; //创建一个指向指向结构体的指针
CreateListHead(&list2, 10);
Listinsert(&list2, 1, 999);
Listinsert(&list2, 1, 1000);
Listinsert(&list2, 1, 1200);
//遍历并输出该链表上的所有数据
cout << "遍历并输出该链表上的所有数据:" << endl;
LinkList p,q;
p = list2->next;
while (p)
{
cout << p->data<<" ";
q = p->next;
p = q;
}
cout << endl;
int e = 0;
ListDelete(&list2, 2,&e);
//遍历并输出该链表上的所有数据
cout << "遍历并输出该链表上的所有数据:" << endl;
p = list2->next;
while (p)
{
cout << p->data << " ";
q = p->next;
p = q;
}
cout << endl;
delete list2;
system("pause");
return 0;
}
结果输出:
遍历并输出该链表上的所有数据:
1200 1000 999 3 72 69 72 77 3 92 61 8 36
遍历并输出该链表上的所有数据:
1200 999 3 72 69 72 77 3 92 61 8 36
请按任意键继续. . .