线性表
线性表的顺序表示和实现
顺序存储定义:在物理上相邻的存储单元中的存储结构,地址连续,即占用一片连续的存储空间
顺序表中元素存储位置的计算:
如果每个元素占用8个内存空间,ai的存储位置是2000单元,则ai+1存储位置是2008 ai+2 = 2000 + 2 * 8,ai+3 = 2000 + 3 * 8
顺序表的特点: 以物理位置相邻,任一元素均可随机存取
线性表长可变(删除),而数组长度不可动态定义 用一变量表示顺序表的长度属性
#define LIST_INIT_SIZE 100 // 线性表存储空间的初始分配量
typedef struct{
ElemType elem[LIST_INIT_SIZE];
int length; // 当前长度
}SqList;
例:
图书表的顺序存储结构类型定义
#define MAXSIZE 10000
typedef struct
{
char no[20];
char name[50];
float price;
}Book;
typedef struct
{
Book *elem;
int length;
}Sqlist;
类c语言操作的补充
数组补充:
数组静态分配:
tpyedef struct
{
ElemType data[Maxsize];
int length;
}Sqlist;
动态分布:
tpyedef struct
{
ElemType *data;
int length;
}Sqlist;
// 然后进行动态内存的分配
Sqlist L;
L.data = (ElemType *)malloc(sizeof(ElemType)*MAXSIZE);
c++的动态存储分配
new 类型名T (初值列表) 申请内存空间
int *p1 = new int; 或 int *p1 = new int(10);
释放内存空间
delete *p1`
线性表的基本操作
1.线性表的初始化
Status InitList_Sq(SqList &L) //构造一个空的顺序表L
{
L.elem = new ElemType[MAXSIZE]; // 为顺序表分配空间
if(!L.elem) // 存储分配失败
exit(OVERFLOW;
L.length = 0;//空表的长度为0
return OK;
}
2.摧毁线性表
void DestroyList(SqList &L)
{
if (L.elem)
delete L.elem //释放储存空间
}
3.清空线性表
void ClearList(SqList &L)
{
L.length = 0;//将线性表的长度变为0
}
4.求线性表的长度
int GetLength(SqList L)
{
return L.length;
}
5.判断线性表是否为空
int IsEmpty(SqList L)
{
if (L.length == 0)
return 1;
else
return 0;
}
6.线性表的取值
int GetElem(SqList L,int i,ElemType &e)
{
if (i < 1 || i > L.length)
return error;//判断i值是否合理
e = L.elem[i-1];//第i-1的存储单元存储着第i个数据
return OK;
}
7.顺序表的查找
int LocateElem(SqList L,ElemType e)
{//在线性表L中查找值为e的数据元素,返回其序号
for (i = 0;i < L.length;i++)
{
if(L.elem[i] == e)
return i+1;//查找成功,返回序号
return 0;//查找失败,返回0
}
}
平均查找长度(n + 1) / 2
8.顺序表的插入
算法思想: 1.插入位置是否合法,假设有n个元素,则他可插入的位置为1~n+1
2.判断顺序表的储存空间是否已满,若已满返回ERRPOR
3.将第n至第i位的元素依次向后移动一个位置,空出第i个位置
4.将要插入的新元素e放入第i个位置
5.表长加一,成功返回OK
Status ListInsert_Sq(SqList &L,int i,ElemType e)
{
if (i < 1 || i > L.length + 1)
return ERROR;
if (L.length == MAXSIZE)
return ERROR;
for (j = L.length - 1;j >= i - 1;j--)
L.elem[j+1] = L.elem[j];
L.elem[i-1] = e;
L.length++;
return OK;
}
9.顺序表的删除
算法思想:
1.判断删除位置是否合法,合法位置为1~n
2.将欲删除的元素保留在e中
3.将第i+1至第n位的元素依次向前移动一个位置
4.表长减一
Status ListDelete Sq(SqList &L,int i)
{
if (i<1 || i >L.length)
return ERROR
for (j=i;j<=L.length-1;j++)
L.elem[j-1] = L.elem[j];
L.length--;
return OK;
}
线性表的链式表示和实现
单链表
结点只有一个指针域的链表
双链表
结点有两个指针域的链表
循环链表
首尾相接的链表
链表的存储结构示意图有两种:
1.不带头节点
2.带头结点
好处: 1.便于首元结点的处理
首元结点地址保存在头节点的指针域中,所以在链表的第一个位置上的操作和其他位置一致
2便于空表与非控表的统一处理
无论链表是否为空,头指针都是指向头结点的非空指针,因此空表与非空表的处理也就统一了
头节点的数据域内装的是什莫?
可以为空,也可以为表长,头节点不计入表长
单链表
带头结点的单链表
初始化
typedef struct Lnode{ // 声明节点类型和指向节点的指针类型
ElemType data; //节点的数据域
struct Lnode *next; //节点的指针域
}Lnode,*LinkList;//LinkList为指向结构体Lnode 的指针类型
定义链表
LinkList L;
定义结点指针
LinkList p;
例:存储学生姓名学号成绩的单链表
一般情况下我们会这样定义
typedef struct student{
char name[8];
char no[8];
int score;
struct student *next;
}Lnode,*LinkList;
为了统一链表的操作,我们这样定义
typedef struct student{
char name[8];
char no[8];
int score;
}ElemType;
typedef struct Lnode{
ElemType data;
struct Lnode *next;
}Lnode,*LinkList;
1.初始化函数
算法步骤:
1.生成新节点做头节点,用头指针L指向头节点
2.将头结点的指针域置空
Status LnitList_L(LinkList &L){
L = new LNode ;
L->next = NULL;
return OK;
}
2.判断链表是否为空(头节点和头指针仍然在)
int ListEmpty(LinkList L){
if (L->next)
return 0;
else
return 1;
}
3.单链表的销毁
Status DestroyList_L(LinkList &L){
LinList p;
while(L){
p = L;
L = L->next;
delete p;
}
return OK;
}
4.清空链表
Status ClearList(LinkList &L){
LinkList *p,*q;
p = L->next;
while(p){ //没到表尾
q = p->next;
delete p;
p = q;
}
L->next = NULL;// 头结点的指针域为空
return OK;
}
5.计算单链表的表长
从首元结点开始,依次计算所有结点
cong'shou'yuan
int ListLength_L(LinkList L)
{
int i = 0
LinkList p;
p = L->next;
while(p)
{
i++;
p = p-next;
}
return i;
}
重要操作
1.p指向头结点
p = L;
2.s指向首元结点
s = L -> next
3.p指向下一节点
p = p ->next
6.取值--去单链表中第i个元素
链表不是随机存取结构
算法步骤
1.从第一个结点开始(L->next)顺链扫描,用指针p指向当前扫描到的结点,p初值p = L->next
2.j做计数器,累计当前扫描过的结点数,j初值为1
3.当p指向扫描到的下一节点时,计数器j加1
4.当j==i时,p所指的结点就是要找的第i个结点
具体实现:
Status GetElem_L(LinkList L;int i;Elem Type &e)//获取线性表L中的某个数据元素的内容,通过变量e返回
{
p = L->next;//进行初始化
j = 1;
while(p&&j<i){
p = p->next;//向后扫描,直到p指向第i个元素或p为空
j++;
}
if (!p || j > i)//第i个元素不存在
{
return ERROR;
}
e = p->data;
return OK;
}
7.单链表的查找--按值查找
- 返回地址
算法步骤
1 .从第一个结点起,依次和e相比较
- 如果找到一个其值与e相等的数据元素,则返回其在链表中的位置或地址;
3.如果查遍整个链表都没有找到其值和e相等的元素,则返回0或NULL
Lnode *LocateElem_L(LinkList L,ELemtype e){
p = L->next;
while(p&&p->data != e)
{
p = p->next;
}
return p;
}
2.返回位置序号
Lnode *LocateElem_L(LinkList L,ELemtype e){
p = L->next;
while(p&&p->data != e)
{
p = p->next;
j++;
}
if(p)
{
return j;
}
else
{
return 0;//查找失败返回0
}
return p;
}
7.插入--在第i个结点前插入值为e的新结点
算法步骤:
1.首先找到ai-1的储存位置p
2.生成一个数据域为e的新节点s
3.插入新节点:新节点的指针域指向结点ai;结点ai-1的指针域指向新节点
s -> next = p -> next
p -> next = s;//s里存的是新插入结点的地址
先执行下面一句可以吗?
可以,但是需要一个中间变量
实现
Status ListLnsert_L(LinkList &L, int i,ElemType e)
{
p = L;
j = 0;
while(p && j < i -1){//寻找第i-1个结点,p指向i-1结点
p = p-next;
++j;
}
if (!p || j > i -1)//i大于表长+1或者小于1,插入位置非法
return ERROR;
s = new LNode;
s->data = e;
s->next = p->next;
p->next = s;
return OK;
}
8.删除--删除第i个结点
算法步骤
1.首先找到ai-1的存储位置p,保存要删除的ai的值
2.令p->next指向ai+1
p->next = p->next->next
3.释放结点ai的空间
Status ListDelete_L(LinkList &L, int i; ElemType &e)
{
p = L;
j = 0;
while(p->next && j < i-1)
{
p = p->next;
++j;
}
if(!(p->next) || j > i-1)
return ERROR;
q = p->next;//临时保存被删结点的地址以备释放
p->next = q->next; //改变删除节点前驱结点的指针域
e = q->data;//保存删除节点的数据域
delete q;
return OK;
}
9.单链表的建立
1.头插法--元素插入在链表头部
void CreateList_H(LinkList &L,int n){
L = new Lnode;
L->next = NULL;//先建立一个带头结点的单链表
for (i = n;i > 0;--i)
{
p = new Lnode;
scanf("%d",&p->data);
p->next = L-next;//插入表头
L->next = p;
}
}
2.尾插法
从一个空表开始,将新节点逐个插入到链表的尾部
void Create_R(LinkkList &L, int n);
{
L = new Lnode;
L->next = NULL;
r = L;//尾指针r指向头结点
for (i = 0;i < n;i++)
{
p = new Lnode;
scanf("%d",&p->data);
p->next = NULL;
r-next = p; //插入到表尾
r = p; //r指向新的尾结点
}
}
循环链表
表中最后一个节点的指针域指向头结点,整个链表形成一个环
循环链表不像单链表一样有NULL,故遍历循环链表时,判断依据是判断他们是否等于头指针
将两个循环链表合并(将Tb合并在Ta之后)
p存表头结点
Tb表头连接到Ta表尾
释放Tb表头结点
修改指针
LinkList Connect(LinkList Ta,LinkList Tb){
p = Ta->next;
Ta->next = Tb->next->next;
delete Tb->next;
Tb->next = p;
return Tb;
}
双链表
在单链表的每一个节点里再增加一个指向其直接前驱的指针域proior,这样链表中就形成了有两个方向不同的链
结构定义
typedef struct DuLNode{
ElemType data;
struct DuLNode *proior,*next;
}DuLnode,*DuLinkList;
双链表头结点的proior == NULL
尾结点的next==NULl
而双向循环链表 头结点的proior指向链表的最后一个结点 最后一个结点的next指向头结点
1.插入
void ListInsert_DuL(DuLinkList &L,int i,ElemType e)
{//在带头结点的双向循环链表L中第i个位置之前插入元素e
if(!p = GetElemP_DuL(L,i))
return ERROR;
s = new DuLNode;
s-data = e;
s->prior = p->prior;
p->prior->next = s;
s->next = p;
p->prior = s;
return OK;
}
2.删除
void ListDelete_DuL(DuLink &L,int i,ElemType &e){
//删除带头结点的双向循环链表L的第i个元素,并用e返回
if (!(p=GetElemP_DuL(L,i)))
return ERROR;
e = p->data;
p->prior->next = p->next;
p->next->prior = p->prior;
free(p);
return;
}
顺序表和链表的比较
链式存储结构的优点:
1.节点空间可以动态申请和释放
2.数据元素的逻辑次序靠结点来指示,插入和删除时不需要移动数据元素
缺点:
1.存储密度小。每个结点的指针域需额外占用储存空间,当每个结点的数据域所占字节不多时,指针域所占储存空间的比重显得很大。
2.非随机存取
存储密度越大,存储空间的利用率就越高
线性表的应用
线性表的合并
假设利用连哥哥线性表La和Lb分别表示两个集合A和B,现要求一个新的集合A = A并B
两个集合中也会有相同的,新集合中没有相同的元素
void union(List &La,List Lb)
{
La_len = ListLength(La);
lb_len = ListLength(lb);
for (i = 1;i <= Lb_len;i++)
{
GetElem(Lb,i,e);
if (!LocateElem(La,e))
ListInsert(&La,++La_len,e);
}
}
有序表的合并
非递减:表中有相同的元素
合并完成后可能有相同的元素
从两个线性表中比较较小的元素加入到新的线性表Lc中 算法步骤 1.创建一个空表Lc 2.依次从La或Lb中摘取元素值较小的结点插入到Lc表的最后,直至其中一个表表为空为止 3.继续将La或Lb其中一个表的剩余节点插入在Lc表的最后