重读数据结构之--线性表
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.结点的定义 对于数据元素a(i)来说,除了存储其本身信息外,还需存储一个指示其后继的信息,存储数据元素信息的域成为数据域,存储直接后继位置的域成为
指针域,两个域组成的元素a(i)成为结点。
2.单链表的定义 n个结点链接成以链表,每个结点只包含一个指针域,所以叫单链表。
2.1 规定线性链表最有一个结点指针为NULL
2.2 在单链表第一个结点前附设一个结点,成为头结点,头结点数据域不存储任何信息,但头结点中的头指针指向第一个结点。
3.单链表的代码表示
4.单链表的优点:如果不知道要插入或者删除位置i的指针的话,单链表也是需要遍历,逼近到i位置,时间复杂度和顺序表没区别,但是,如果从第i个位置开始插
入10个元素
那么单例表优势明显,只需要遍历一次找到位置i,复杂度为O(n),而顺序表每插入一个都需要遍历复杂度为O(n^2) .
5.单链表的创建,代码表示:
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指针即可。
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; }
相关文章推荐
- 重读数据结构——线性表
- 重读数据结构——线性表
- 数据结构线性结构-----线性表
- 数据结构备忘之——线性表
- 数据结构---线性表----顺序存储结构
- 数据结构——线性表
- 数据结构---线性表----单链表结构与顺序存储结构的对比
- 数据结构-线性表
- 数据结构---线性表----静态链表
- 数据结构---线性表----循环链表和双向链表
- 坚持每天至少一百行。练习数据结构线性表
- 数据结构-线性表
- 重读数据结构——严蔚敏C语言版
- php实现数据结构线性表(顺序和链式)
- 数据结构学习-线性表(1)
- 数据结构第二天、线性表的链式表示和实现
- 数据结构 线性表—单链表
- 数据结构和算法笔记---线性表
- C语言数据结构学习之数组线性表
- 数据结构-线性表