线性表
1.线性结构
线性结构是一种简单的数据结构,其特点包含以下四点:
- 存在唯一的一个“第一个”元素
- 存在位移的一个“最后一个”元素
- 除第一个元素外,其余元素均只有一个前驱
- 除最后一个元素外,其余元素均只有一个后驱
2.线性表
2.1线性表定义
线性表是线性结构的一种,也是最简单和最常用的一种数据结构。它是n个数据元素的有限序列。常见的线性表操作有:
- 初始化
- 销毁
- 清除
- 判空
- 插入
- 删除
- 合并
- …
2.2 线性表的实现
线性表的实现分为两种:顺序表和链表,两种方式各有所长,各有所短。在实际使用中可根据应用场景进行选择。
先将文本中用到的公有头文件贴上,主要是定义了一些状态宏。
//
// Created by 张兴锐 on 2018/7/25.
//
#ifndef DATASTRUCTURE_PUBLIC_H
#define DATASTRUCTURE_PUBLIC_H
// 函数结果状态代码
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASLIBLE -1
#define OVERFLOW
typedef int Status;
typedef int ElemType;
#endif //DATASTRUCTURE_PUBLIC_H
现在,先来介绍一下顺序表:
3.顺序表
顺序表指的是指数据之间逻辑地址连续&物理地址连续的线性表结构。如下图:
顺序表的优点在于随机可存取表中任意元素,读取时间在 ,不过缺点在于每次插入和删除都需要移动较多元素(下文中有时间复杂度的推导),同时由于不知道需要多大的存储空间,在开始往往需要较大的存储空间。
顺序表操作实现:
3.1 定义
//----------------线性表定义-----------
#define LIST_INIT_SIZE 100 //线性表初始化大小
#define LIST_INCREMENT 10 //线性表存储空间每次分配增量
typedef struct{
ElemType *elem; //线性表的基地址(首元素地址)
int length; //当前长度
int listsize; //当前分配的容量
}Sqlist;
3.2初始化
Status initList_Sq(Sqlist &L){
//构造一个空的线性表
L.elem = (ElemType *)malloc(LIST_INIT_SIZE*sizeof(ElemType));
if(!L.elem) exit(ERROR) ;
L.length = 0;
L.listsize = LIST_INIT_SIZE;
return OK;
}
3.3插入
在顺序表的第i个位置前插入元素,将第i个元素到最后一个元素整体向后移动。
/**
* 在顺序表的第i个位置前插入元素
* @param L 将要进行操作的线性表
* @param i 在第i个位置之前进行插入
* @param e 插入的值
* @return 插入是否成功:
* OK 成功
* ERROR 失败
*/
Status ListInsert_Sq(Sqlist &L,int i,ElemType e){
//i的合法位置 1<=i<=L.length + 1
if(i < 1 || i> L.length + 1) exit(ERROR); //插入位置不合法
if(L.length >= L.listsize){ //空间已满,增加空间
ElemType *newbase = (ElemType *)realloc
(L.elem,(L.listsize+LIST_INCREMENT)*sizeof(ElemType));
if(!newbase) exit(ERROR); //分配地址失败
L.elem = newbase; //新基地址
L.listsize+=LIST_INCREMENT; //增加容量
}
ElemType *q = &L.elem[i-1]; //q为插入位置
//从第length个元素到第i各元素进行搬移
for(ElemType *p = &L.elem[L.length-1];p>= q;p--){
*(p+1) = *p; //向后搬移
}
*q = e;
L.length++;
return OK;
}
3.4 删除
在顺序表L中删除第i个元素,以e作为返回值,将第i+1到最后一个元素整体前移。
/**
* 在顺序表L中删除第i个元素,以e作为返回值
* @param L
* @param i
* @param e
* @return OK
* ERROR
*/
Status ListDelete_Sq(Sqlist &L,int i,ElemType &e){
if(i< 1 || i>L.length) exit(ERROR);//删除位置错误
ElemType *q = &L.elem[i-1]; //将要删除的位置地址
e = *q;
ElemType *p = L.elem + L.length -1; //表尾地址
for(;q<p;q++){
*q = *(q+1);
}
L.length--;
return OK;
}
3.5 合并
将线性表a和线性表b合并到a中,只合并b中有而a中没有的元素。
int LocateElem(Sqlist &l,int e,bool (*compare)(ElemType,ElemType)){
int i = 1;
ElemType *q = l.elem;
while(i <= l.length && !(*compare)(*q++,e)) i++;
if(i<= l.length) return i;
else return 0;
}
bool equal(ElemType a,ElemType b){
if(a == b){
return TRUE;
}
return FALSE;
}
/**
* 将线性表a和线性表b合并到a中,只合并b中有而a中没有的元素
* @param a
* @param b
*/
void Merge1(Sqlist &a,Sqlist b){
for(int i = 0;i<b.length;i++){
int e = b.elem[i];
if(!LocateElem(a,e,equal)){
ListInsert_Sq(a,a.length+1,e);
}
}
}
3.6 插入与删除的时间复杂度
假设
是在第i个元素之前插入一个元素的概率,则在长度为
的线性表中插入一个元素时所需要移动元素次数的期望为:
令 ,则
假设 是删除第 个元素的概率,则在长度为 的线性表中删除一个元素时所需移动元素次数的期望为:
令 ,则
4.链表
链式结构不要求逻辑上相邻的元素在物理位置上也相领,因此他没有顺序存储结构所有的弱点(插入删除的移动性),但同时也失去了顺序表可随机存取的优点。链表由数据域和指针域构成。其结构如下:
4.1 定义
//----------------链表定义------------
typedef struct LNode{
ElemType data;
struct LNode *next;
}*LinkList;
4.2 插入
链表在第i个位置前插入元素e。
/**
* 链表在第i个位置前插入元素e
* @param head
* @param i
* @param e
* @return
*/
Status LinkListInsert(LinkList &head,int i,ElemType e){
LinkList p = head;
int j = 0;
while(p && j< i-1) {
p = p->next;
j++;
}
if(!p || j> i-1){
exit(ERROR);
}
LinkList v = (LinkList)malloc(sizeof(LNode));
v->data = e;
v->next = p->next;
p->next = v;
head->data++;
return OK;
}
过程如下:
4.3 删除
删除链表第i个元素,并以e作为返回
/**
* 删除链表第i个元素,并以e作为返回
* @param head
* @param i
* @param e
* @return
*/
Status LinkListDelete(LinkList &head,int i,ElemType &e){
LinkList p = head;
int j = 0;
while(p && j<i-1){ //找到第i-1个元素
p = p->next;
j++;
}
if(!p || j>i-1){
exit(ERROR);
}
LinkList q = p->next;
e = q->data;
p->next = q->next;
free(q); //释放空间
head->data--;
return OK;
}
其过程如下:
4.4 创建链表
/**
* 创建链表
* @param head 头结点,数据域存储整个链表的长度
* @return OK
* ERROR
* @input 输入-1作为链表结束
*/
Status LinkListCreate(LinkList &head){
head = (LinkList)malloc(sizeof(LNode));
head->data = 0;
head->next = NULL;
ElemType e;
cin>>e;
while(e != -1){
/* LinkList p = (LinkList)malloc(sizeof(LNode));
p->data = e;
p->next = NULL;
head->next = p;
head->data++;*/
LinkListInsert(head,head->data+1,e);
cin>>e;
}
return OK;
}
4.5 合并链表
将有序链表a,b合并为链表c(这里的有序假设为不递减)
/**
* 将有序链表a,b合并为链表c(这里的有序假设为不递减)
* @param a
* @param b
* @param c
*/
void MergeList(LinkList &a,LinkList &b,LinkList &c){
LinkList p_a = a->next;
LinkList p_b = b->next;
c = a; //初始设定a为头结点
c->data = a->data+b->data;
LinkList p_c = c;
free(b);
b = NULL;
while(p_a && p_b){
if(p_a->data <= p_b->data){
p_c->next = p_a;
p_c = p_a;//p_c = p_c->next;
p_a = p_a->next;
}else{
p_c->next = p_b;
p_c = p_b;//p_c = p_c->next;
p_b= p_b->next;
}
}
p_c->next = p_a?p_a:p_b;
}
5. Others’链表
- 将尾指针再指向头指针,则变为了循环链表。
- 在定义结构中加入前驱指针,可变为双向链表。
这两种方式都是在单链表上的拓展,实现并不复杂,这里就不贴出具体代码了。
6.总结
总体来说,线性表的两种方式并不复杂,写代码的时候可以在草稿纸上画上一个线性表样,跟着写就好了。