您的位置:首页 > 理论基础 > 数据结构算法

重读数据结构之--线性表

2013-12-06 23:02 459 查看
三 线性表(List)

1. 定义

零个或多个数据元素的有限序列。元素间是有顺序的,元素个数是有限的,除第一个元素外,每个元素都有且只有一个前驱元素,除最后一个元素外,

每个元素都有且只有一个后继元素。

2. 线性表的抽象数据类型定义

①线性表中有一个集合,集合中每个元素都是DataType类型,每个元素都只有一个前驱和后继,第一个无前驱,最后一个无后继。

②提供以下操作:

1.初始化线性表,建立一个空表L。 InitList(*L)

2.判断当前表是否为空,返回判断结果bool值。 ListEmpty(L)

3.将线性表清空。 ClearList(*L)

4.获取并返回指定i位置处的元素e。 GetElem(L,i,*e)

5.判断某元素是否在表内,有返回其位置,无返回0. LocateElem(L,e)

6.在i处插入元素e. ListInsert(*L,i,e)

7.删除第i个元素,返回该值e。 ListDelete(*L,i,*e)

8.返回线性表L的元素个数。 ListLength(L)

3.线性表的两种物理结构

① 顺序存储结构

1.定义 用一段地址连续的存储单元依次存储线性表的数据结构。

2.结构体表示如下:

3.顺序结构特点

① 用数组来存放顺序表,意味着一开始要分配固定长度的数组,鉴于表随时会插入或删除,数组空间一定要大于当前表长度。

② 由于数组的特性,在顺序表中取出i位置元素或者在i位置存入一个元素,时间复杂度都是O(1)

③ 线性表规定起始位置从1开始而不是0,但其内部的数组是从0开始的,所以元素的位置序号和真实存放元素的数组角标不一样,表的某位置i上元素始终等于

数组中第i-1个元素

4.顺序结构的插入,删除,获取,代码实例如下:

/**
1.初始化线性表,建立一个空表L。                InitList(*L)
2.判断当前表是否为空,返回判断结果bool值。      ListEmpty(L)
3.将线性表清空。                               ClearList(*L)
4.获取并返回指定i位置处的元素e。               GetElem(L,i,*e)
5.判断某元素是否在表内,有返回其位置,无返回0. LocateElem(L,e)
6.在i处插入元素e.                              ListInsert(*L,i,e)
7.删除第i个元素,返回该值e。                   ListDelete(*L,i,*e)
8.返回线性表L的元素个数。                      ListLength(L)
/

/*线性表的顺序存储结构体*/
#define MAXSIZE 20
typedef int ElemType;
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
typedef int Status;
Status e;

//顺序表的定义
typedef struct{
ElemType data[MAXSIZE]; /*用数组存放元素,数组长度为确定不变值*/
int length;             /*表的当前真实长度,是一个变化值*/
}SqList;

//判断顺序表是否为空
int ListEmpty(SqList L){
if(L.length<1)
return TRUE;

return FALSE;
}

//顺序表获取第i个元素
Status  GetElem(SqList L,int i,ElemType *e){
if( ListEmpty(L)||i<1||i>L.length )
return ERROR;

*e=L.data[i-1];
return OK;
}

//顺序表的插入
Status  ListInsert(SqList L,int i,ElemType e){
/**
线性表长度到了数组最大值说明表已满了,这也是length的极值了。
插入点i可以是lenth那个位置,即在最后面加是可以的,只要此时没到length的极值,
但是在表最后以外的位置或者表头之前则不可以插入了
*/
if(L.length == MAXSIZE || i<=0 || i>L.length+1)
return ERROR;

if(i<L.length){   	/*如果要插入的位置不在表尾*/
/*由于插入的位置始终比此位置处数组的角标值大1,所以k代表顺序表中最后一个值的数组角标
i-1正好是插入位置的数组角标,从插入位置到表尾的每一个值都往后移动一位*/
for(int k=L.length-1;k>i-1;k--)
L.data[k+1]=L.data[k];	/*每一个数都等于它前一个数,倒叙遍历解决了值覆盖的问题*/
}
L.data[i-1]=e;/*如果插入位置正好是表尾部,那么i-1正好对应数组有效尾部下标,直接赋值即可*/
L.length++;/*最后记得修改表长度*/
return OK;

/*****时刻注意 <数组角标=顺序表位置-1> 是不出错的关键******/
}

//顺序表的删除
Status ListDelete(SqList *L,int i,ElemType *e){
if(L->length==0||i<=0||i>L->length)
return ERROR;

*e=L->data[i-1];
if(i<L->length){
for(int k=i;k<=L->length-1;k++){ /* 边界判断一定注意是"<"还是"<=" */
L->data[k-1]=L->data[k];
}
}
L->length--;
return OK;
}


② 链式存储结构

1.结点的定义 对于数据元素a(i)来说,除了存储其本身信息外,还需存储一个指示其后继的信息,存储数据元素信息的域成为数据域,存储直接后继位置的域成为

指针域,两个域组成的元素a(i)成为结点。

2.单链表的定义 n个结点链接成以链表,每个结点只包含一个指针域,所以叫单链表。

2.1 规定线性链表最有一个结点指针为NULL

2.2 在单链表第一个结点前附设一个结点,成为头结点,头结点数据域不存储任何信息,但头结点中的头指针指向第一个结点。

3.单链表的代码表示

4.单链表的优点:如果不知道要插入或者删除位置i的指针的话,单链表也是需要遍历,逼近到i位置,时间复杂度和顺序表没区别,但是,如果从第i个位置开始插

入10个元素

那么单例表优势明显,只需要遍历一次找到位置i,复杂度为O(n),而顺序表每插入一个都需要遍历复杂度为O(n^2) .

5.单链表的创建,代码表示:

//单链表结构体定义
typedef struct Node{ //这里的Node是结构体的数据类型名
ElemType data;
struct Node *next;      //定义一个该类型的指针,指向下一个该类型数据
} Node;              //这里的Node是结构体的一个实例
typedef struct Node *LinkList;//定义指向该实例的指针,这个指针最重要的意义是它代表头结点的头指针!

//单链表是否为空
int ListEmpty(LinkList L){
if(L->next==NULL)
return TRUE;

return FALSE;
}

//单链表获取第i个元素
Status  GetElem(LinkList L,int i,ElemType *e){
LinkList p;  //声明一个结点
p=L->next;   //p结点表示L中的第一个结点,也就是头结点指向的那个结点
int j=1;       //计数器
while (p && j<i){ //如果p是第一个结点而且存在,本次运算完后j此时为2,所以j=i时,判断条件里的p是第i-1个结点,而语句体p=p->next的p才是第i个结点
p=p->next;    //j=i-1时,条件满足,进入循环,此时p是第i个结点,同时++j=i,退出循环
++j;          //代表下一个p的位置
}

if(!p || j>i){   //如果表长度本身就小于i,则p为空退出循环,所以只要j<i时p指向NULL则说明i不在表范围内
return ERROR;
}
*e=p->data;
return OK;
}

//单链表插入元素:在第i个元素前插入新的结点,也就是把第i-1个结点的指向改变!
Status  ListInsert(LinkList L,int i,ElemType e){
if(i<=0)
return ERROR;
LinkList p;
p=L;   //p是头指针
int j=1;
/*循环逼近第i-1个结点,也就是插入点前面的那个结点*/
while (p && j<i){  //判断j值,j=1时,下面p是第一个结点,所以j=i-1时p是j-1个结点,因此循环结束时p是第i-1个结点!
p=p->next;
++j;
}
if(!p || j>i)      //插入位置不在表内时
return ERROR;

LinkList n=(LinkList)malloc(sizeof(LinkList)); //新分配一个结点
n->data=e;         //赋初值
n->next=NULL;

/*为避免指向覆盖问题,必须先让新节点指向老结点原来指向的位置,再把老结点指向新节点*/
/*单链表插入操作顺序:先新后旧*/
n->next=p->next;
p->next=n;
return OK;
}

//单链表的删除,删除第i个结点
Status ListDelete(LinkList L,int i,ElemType *e){
if(i<=0)
return ERROR;

LinkList p;
p=L;
int j=1;

while (p && j<i){  //逼近到要删除的位置i-1处
p=p->next;
++j;
}

if(!p || j>i)      //此时p->next是第i个位置的元素
return ERROR;

LinkList q=p->next;
p->next=q->next; //把指向i位置的指针指向i+1位置

*e=q->data;
free(q);

return OK;
}

//单链表的创建
LinkList CreateListHead(int n){
LinkList L=(LinkList)malloc(sizeof(LinkList));//创建并初始化头结点,L就代表一个空的待填充的链表
int i;
srand(time(0));//初始化随机数种子,保证每次运行都不同
L->data=NULL;
L->next=NULL;

#if 1 //头插法
for(i=0;i<n;i++){
LinkList n=(LinkList)malloc(sizeof(LinkList));//创建新结点
n->data=rand()%100+1;
n->next=L->next;//始终从头部插入新元素,充分利用头指针的作用
L->next=n;
}
return L;
#elif 0//尾插法

LinkList last=L;//取整个链表的一个小结点last,始终将其作为链表的尾结点,last=p说明了归属性,last属于整个链表一部分
for(i=0;i<n;i++){
LinkList n=(LinkList)malloc(sizeof(LinkList));//创建新结点
n->data=rand()%100+1;
last->next=n;//始终指向新加入的结点
last=n;      //新加入的结点变成尾结点,如同传递一个交接棒
}
last->next=NULL;//赋值完毕后,尾部指向空代表链表结束,此时L填充完成
return L;
#endif
}

//单链表的整表删除
Status ClearList(LinkList L){
LinkList p,q;
p=L->next;//p指向第一个结点
if(!p)
return ERROR;

while(p){ //p不为空
q=p->next; //p的下一个结点用临时变量q记录下来,因为马上要释放p了
free(p);   //把p释放
p=q;       //把p重新指向p的下一个变量,也就是临时变量q,重新进入循环
}
printf("delete ok \n");
return OK;
/*q用来辅助p的释放,记录下一个是谁*/
}

//单链表的遍历打印
Status PrintList(LinkList L){
LinkList p;
p=L->next;
if(!p){
printf("error");
return ERROR;
}
while(p){
printf("%d ",p->data);
p=p->next;
}
printf("\n");
return OK;
}


4.循环链表

①定义 将单链表中终端结点的指针由指向空改为指向头结点,则可使单链表成为环,即为循环链表。

②特性 1.如果循环链表为空,则头结点指向自己 2.遍历循环链表一次的边界判断是p->next不等于头结点

③变种

1.带尾指针的循环链表 用指针rear指向尾结点。

2.优势 3.2.1 循环链表最后一个结点是rear,则第一个结点就是rear->next->next,可以快速定位头尾

3.2.2 可以快速合并两个链表,比如rearA 和rearB是两个链表的尾指针,合并操作如下:

①临时记录A的头结点:p=rearA->next ②临时记录B的头结点 q=rearB->next

③让A的尾指针指向B的第一个有效结点:rearA->next=rearB->next->next

④让B的尾指针指向A的头结点:rearB->next=p ⑤释放B的头结点,它被干掉了free(q)

5.双向链表

①定义 在单链表的每个结点中再设置一个指向其前驱的指针,即有两个指针域

②结构体表示

③特性 1.空的双向链表,两个指针都指向头结点 2.对于获取链表长度,查找元素,获取位置等,只操作next指针即可。

//定义双向链表的结点,有了结点即有了链表
typedef struct DulNode{  //类型名
ElemType data;
struct DulNode *prior; //加struct区分下面的实例名,表明这里是类型
struct DulNode *next;
} DulNode,*DuLinkList;  //实例名,指针名

//双向链表的插入,在第i个位置前插入一个元素e
Status  ListInsert(DuLinkList L,int i,ElemType e){
if(i<=0)
return ERROR;
DuLinkList p;
p=L;   //p是头指针
int j=1;
/*循环逼近第i-1个结点,也就是插入点前面的那个结点*/
while (p && j<i){  //判断j值,j=1时,下面p是第一个结点,所以j=i-1时p是j-1个结点,因此循环结束时p是第i-1个结点!
p=p->next;
++j;
}
if(!p || j>i)      //插入位置不在表内时
return ERROR;

DuLinkList n=(DuLinkList)malloc(sizeof(DuLinkList)); //新分配一个结点
n->data=e;         //赋初值

/*双向链表插入操作顺序:左右,后前*/
n->prior=p;
n->next=p->next;
p->next->prior=n;
p->next=n;
return OK;
}

//双向链表的删除操作,删第i个元素
Status ListDelete(DuLinkList L,int i,ElemType *e){
if(i<=0)
return ERROR;
DuLinkList p;
DuLinkList q;
p=L;
int j=1;
/*循环逼近第i-1个结点,也就是插入点前一个结点*/
while (p && j<i){
p=p->next;
++j;
}
if(!p || j>i)
return ERROR;

/*删除操作规则:前后*/

q=p->next;//位置i处的结点
p->next=q->next;
q->next->prior=q->prior;
free(q);
return OK;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: