第二章 线 性 表(顺序表、单链表、静态链表、循环链表、双向链表)
来源:互联网 发布:暗黑破坏神2数据库 编辑:程序博客网 时间:2024/05/10 06:35
1. 线性表:是由n(n≥0)个数据元素组成的有限序列(数据元素的非空有限集合)。
若将线性表记为(a1,...,ai-1,ai,ai+1,...,an),则表中ai-1领先于ai,ai领先于ai+1,称ai-1是ai的直接前驱元素,ai+1是ai的直接后继。当i=1,2,...,n-1时,ai有且仅有一个直接后继,当i=2,3,...,n时,ai有且仅有一个直接前驱。
线性表元素的个数n(n>=0)定义为线性表的长度,当n=0时,称为空表。
在较复杂的线性表中,一个数据元素可以有若干个数据项组成。
2. 抽象数据类型线性表的定义:
ADT { 数据对象:D = {ai|ai∈Element,i = 1,2,3,...,n, n>=0}; 数据关系:R = {<ai-1,ai>| ai-1,ai∈D,i = 2,3,...,n}; 基本操作: InitList(&L);//构造一个空的线性表 DestroyList(&L);//L已存在;销毁线性表L; ClearList(&L);//L已存在;把L重置为空表; ListEmpty(L);//若L为空表,返回TRUE;否则返回FALSE; ListLength(L);//返回L中元素的个数; GetElem(L,i,&e);//返回第i个元素赋给e; LocateElem(L,e,compare());//返回L中第一个与e满足函数compare()的元素的位序; PriorElem(L,cur_e,&pre_e);//返回cur_e的前驱元素赋给pre_e; NextElement(L,cur_e,&next_e);//返回cur_e的后继元素赋给next_e; ListInsert(&L,i,e);//在L中第i个位置插入元素e;L长度加1; ListDelete(&L,i,e);//删除L中第i个元素,并用e返回其值;L长度减1; ListTraverse(L,visit());//依次对L中每个元素调用函数visit();} ADT List
<span style="font-family:'宋体';font-size: 10.5pt;"></span><span style="font-family:'宋体';font-size: 10.5pt;"></span>
2.0 求两个线性表La和Lb的并集
//扩大La,将存在于Lb中且不存在与LA中的元素依次插入LA中(取LB中元素在LA中查访)void union(List &La, List Lb) { La_len = ListLength(La); Lb_len = ListLength(Lb); for(int i = 1; i<=Lb_len; i++) { GetElem(Lb, i, e); if(!LocateElem(La, e, equal)) ListInsert(La, ++La_len, e); }} //union
2.1 合并两个数据元素按值非递减排列的线性表La和Lb,输出Lc
void MergeList (List La, List Lb, List &Lc) { InitList(Lc); int i = j = k = 0; La_len = ListLength(La); Lb_len = ListLength(Lb); while((i<=La_len)&&(j<=Lb_len)) { GetElem(La, i, ai); GetElem(Lb, j, bi); if(ai < bi) { ListInsert(Lc, ++k, ai); ++i; } else {ListInsert(Lc, ++k, bi); ++j;} } while(i<=La_len) { GetElem(La, i++, ai); ListInsert(Lc, ++k, ai); } while(i<=Lb_len) { GetElem(Lb, j++, bi);ListInsert(Lc, ++k, bi); }} //MergeList
顺序表(线性表的顺序存储结构)
1、线性表的顺序存储结构,指的是用一段地址连续的存储单元依次存储线性表的数据元素。
2、存储器中每个单元都有自己的编号,这个编号称为地址。
3、顺序表的三个属性:
Ø 存储空间的起始位置:数组data,它的存储位置就是存储空间的存储位置。
Ø 线性表的最大存储容量:数组长度MaxSize。
Ø 线性表的当前长度:length.
//顺序表:顺序存储结构的线性表(动态分配的一维数组)typedef struct { ElemType data[MAXSIZE]; int length;}SqList;Status Init_SqList(SqList *L);//初始化ElemType GetElem_SqList(SqList L, int i) ;//返回元素Status Insert_SqList(SqList *L, int i, ElemType e);//插入元素ElemType Delete_SqList(SqList *L, int i);//删除指定位置元素并返回值int Search_SqList(SqList L, ElemType e, Status(*compare)(ElemType, ElemType));//查找并返回符合比较条件的元素位置,否则返回-1void Merge_SqList(SqList La, SqList Lb, SqList *Lc);//合并两个顺序表
4、顺序表的存取数据时间复杂度为O(1),插入删除元素时间复杂度为O(n)。
5、顺序存储结构的优缺点:
Ø 优点:1、无须为表示表中元素之间的逻辑关系而增加额外的存储空间。
2、可以快速低存取表中任一位置的元素。
Ø 缺点:1、插入和删除操作需要移动大量元素。
2、当线性表长度变化较大时,难以确定存储空间的容量
3、造成存储空间的“碎片”
6、顺序表上的基本运算(初始化、插入、删除、查找、合并)
//初始化Status Init_SqList(SqList *L) {// L->data = (ElemType*)malloc(MAXSIZE*sizeof(ElemType));// if(!L->data) exit(OVERFLOW); L->length = 0; return OK;}//返回元素ElemType GetElem_SqList(SqList L, int i) { if(i>=0 && i<L.length) return L.data[i]; return -1; //i号元素不存在}//插入元素Status Insert_SqList(SqList *L, int i, ElemType e) { if(L->length == MAXSIZE || i<0 || i>L->length) return ERROR;//顺序表存储空间已满,或指定插入位置无效 int j; for(j=L->length; j>i; j--) L->data[j] = L->data[j-1]; //从i到length-1号元素后移 L->data[j] = e; ++L->length; return OK;}//删除指定位置元素并返回值ElemType Delete_SqList(SqList *L, int i) { if(i<0 || i>L->length-1) return ERROR; ElemType e = L->data[i]; int j; for(j=i; j<L->length-1; j++) L->data[j] = L->data[j+1]; //从i+1到length-1号元素前移 --L->length; return e;}//查找并返回符合比较条件的元素位置,否则返回-1int Search_SqList(SqList L, ElemType e, Status(*compare)(ElemType, ElemType)) { int i = 0; ElemType* p = L.data; while(i<L.length && !(*compare)(*p++, e)) ++i; if(i<L.length) return i; else return -1;}//合并两个顺序表,并按增序排列void Merge_SqList(SqList La, SqList Lb, SqList *Lc) { ElemType* pa = La.data; //pa指向La第一个元素 ElemType* pb = Lb.data; int len = La.length + Lb.length; Lc->length = len < MAXSIZE ? len : MAXSIZE; ElemType* pc = Lc->data; ElemType* pa_last = La.data + La.length-1; //pa_last指向La最后一个元素 ElemType* pb_last = Lb.data + Lb.length-1; ElemType* pc_last = Lc->data + Lc->length-1; while(pa<=pa_last && pb<=pb_last && pc<=pc_last) { if(*pa<=*pb) *pc++ = *pa++; else *pc++ = *pb++; } while(pa<=pa_last && pc<=pc_last) *pc++ = *pa++; //Lb中元素已全部添加到Lc while(pb<=pb_last && pc<=pc_last) *pc++ = *pb++; //La中元素已全部添加到Lc}
单链表
1、链表:为了表示每个数据元素ai与其直接后继数据元素ai+1之间的逻辑关系,对数据元素ai来说,除了其本身的信息之外,还需存储一个指示其直接后继的信息(即直接后继的存储位置)。
我们把存储数据元素的信息域称为数据域,把存储直接后继位置的域称为指针域。指针域中存储的信息称作指针或链。这两部分信息组成数据元素ai的存储映像,成为结点(Node)。
n个结点(ai的存储映像)链结成一个链表,即为线性表(a1,a2,...,an)的链式存储结构,因为此链表的每个结点中只包含一个指针域,所以叫单链表。
//线性表的单链表存储结构(线性链表)typedef struct LNode { ElemType data; struct LNode *next;}LNode, *LinkList;void Creat_LinkList(LinkList *L, int n);//创建以L为指向头结点指针的单链表int Length_LinkList(LinkList L);ElemType GetElem_LinkList(LinkList L, int i);//返回指定位置元素Status Insert_LinkList(LinkList *L, int i, ElemType e);//插入元素Status Delete_LinkList(LinkList *L, int i);//删除元素Status Clear_LinkList(LinkList *L);//单链表的整表删除
2、头指针:链表中第一个结点的存储位置叫做头指针。
Ø 头指针是指链表指向第一个结点的指针,若连标有头结点,则是指向头结点的指针。
Ø 头指针具有标识作用,所以常以头指针冠以链表的名字。
Ø 无论链表是否为空,头指针均不为空,头指针是链表的必要元素。
3、头结点:有时为了更加方便地对链表进行操作,会在单链表的第一个结点前附设一个结点,成为头结点。其数据域可以不存储任何信息,也可以存储如线性表的长度等附加信息;指针域为指向第一个结点的指针。
Ø 头结点是为了操作的统一和方便而设立的,放在第一元素的结点之前,其数据域一般无意义。
Ø 有了头结点,队在第一元素结点前插入结点和删除第一结点,其操作与其他结点的操作就统一了。
Ø 头结点不一定是链表必须要素。
4、单链表的存取数据时间复杂度为O(1),插入删除元素时间复杂度为O(n)。
//创建以L为指向头结点指针的单链表,尾插法void Creat_LinkList(LinkList *L, int n) { int i; LinkList p, q; *L = (LinkList)malloc(sizeof(LNode)); //先建立一个带头结点的单链表,即初始化头结点 (*L)->next = NULL; //数据域无效,指针域为空 if(!(*L)) exit(OVERFLOW); p = *L; //p为指向尾部的结点 for(i=0; i<n; i++) { q = (LinkList)malloc(sizeof(LNode)); //生成新结点 scanf("%d", &q->data); p->next = q; //将表尾终端结点的指针指向新结点 p = q; //将当前的新结点定义为表尾终端结点 } p->next = NULL; //表示当前链表结束,很重要}//返回链表长度int Length_LinkList(LinkList L) { int len = 0; LinkList p = L->next; while(p) { ++len; p = p->next; } return len;}//返回指定位置元素ElemType GetElem_LinkList(LinkList L, int i) { LinkList p = L->next; int j = 0; while(p&&j<i) { //寻找第i个结点 p = p->next; ++j; } if(p && j<=i) return p->data; return ERROR;}//插入元素Status Insert_LinkList(LinkList *L, int i, ElemType e) { int j = 0; LinkList p, q; p = *L; while(p && j<i) { //寻找第i-1个结点 p = p->next; ++j; } if(!p || j>i) return ERROR; //第i个结点不存在 q = (LinkList)malloc(sizeof(LNode)); //生成新结点 q->data = e; q->next = p->next; p->next = q; return OK;}//删除并返回元素ElemType Delete_LinkList(LinkList *L, int i) { int j = 0; LinkList p, q; ElemType e; p = *L; while(p->next && j<i) { //遍历寻找第i-1个结点 p = p->next; ++j; } if(p->next && j<=i) { //第i个结点不存在 q = p->next; p->next = q->next; e = q->data; free(q); return e; } return ERROR;}//将L重置为空表,不删除头结点Status Clear_LinkList(LinkList *L) { LinkList p, q; p = (*L)->next; //p指向第一个结点 while(p) { //没到表尾 q = p->next; free(p); p = q; } (*L)->next = NULL; //头结点指针域为空 return OK;}
静态链表
1、用数组描述的链表叫做静态链表(游标实现法)。
对于一些没有指针的语言(如Basic、Fortran等早期的高级编程语言),无法实现单链表结构。
2、让数组的元素由两个数据域组成,即data和cur。
data:用来存放数据元素。
cur:相当于单链表中的next指针,存放该元素的后继在数组中的下标(cur称为游标)。
//线性表的静态单链表存储结构typedef struct{ ElemType data; //游标(指示器cur)代替指针指示节点在数组中的相对位置 int cur; //数组的第零分量可看成头结点,其指针域指示链表的第一个结点}component, StaticLinkList[MAXSIZE];//数组第一个元素的cur存放备用链表第一个结点的下标;最后一个元素的cur存放链表第一个元素的下标,相当于头结点//即备用链表以0为头结点,MAXSIZE-1为尾结点;链表以MAZSIZE-1为头结点,以0为尾结点void Init_StaticLinkList(StaticLinkList space); //初始化int Length_StaticLinkList(StaticLinkList L); //返回元素个数ElemType GetElem_StaticLinkList(StaticLinkList L, int i); //返回指定元素Status Insert_StaticLinkList(StaticLinkList L, int i, ElemType e); //插入元素ElemType Delete_StaticLinkList(StaticLinkList L, int i); //删除元素int Malloc_StaticLinkList(StaticLinkList space); //分配存储空间void Free_StaticLinkList(StaticLinkList space, int k); //释放内存//将一维数组space中各分量链成一个备用链表,space[0].cur为头指针, "0"表示空指针void Init_StaticLinkList(StaticLinkList space) { int i; for(i=0; i<MAXSIZE-1; i++) space[i].cur = i+1; space[MAXSIZE-1].cur = 0;}//返回静态链表的长度(即元素个数)int Length_StaticLinkList(StaticLinkList L) { int len = 0; int i = L[MAXSIZE-1].cur; //MAXSIZE-1为头结点,i为头指针 while(i) { i = L[i].cur; ++len; } return len;}//返回指定元素ElemType GetElem_StaticLinkList(StaticLinkList L, int i) { int j = 0; int p = L[MAXSIZE-1].cur; while(p && j<i) { //寻找第i个元素 p = L[p].cur; ++j; } if(p && j<=i) return L[p].data; return ERROR;}//插入元素Status Insert_StaticLinkList(StaticLinkList L, int i, ElemType e) { int j, k, p; j = 0; p = MAXSIZE-1; while(p && j<i) { //寻找第i-1号结点 p = L[p].cur; ++j; } if(!p || j>i) return ERROR; k = Malloc_StaticLinkList(L); //分配存储空间 L[k].data = e; L[k].cur = L[p].cur; L[p].cur = k; return OK;}//删除并返回元素ElemType Delete_StaticLinkList(StaticLinkList L, int i) { int j, p, k; ElemType e; j = 0; p = MAXSIZE-1; while(L[p].cur && j<i) { //寻找第i号元素 p = L[p].cur; ++j; } if(L[p].cur && j<=i) { k = L[p].cur; L[p].cur = L[k].cur; e = L[k].data; Free_StaticLinkList(L, k); //释放内存 return e; } return ERROR;}//若备用链表非空,则返回分配的结点下标,否则返回0;int Malloc_StaticLinkList(StaticLinkList space) { int i = space[0].cur; //当前数组第一个元素cur存的值 if(space[0].cur) //space[0].cur == 0则表明备用链表为空 space[0].cur = space[i].cur; return i; //返回第一个备用存储空间的下标}//将下标为k的空闲结点回收到备用链表void Free_StaticLinkList(StaticLinkList space, int k) { space[k].cur = space[0].cur; space[0].cur = k;}
循 环 链 表
1、将单链表中终端结点的指针端由空指针改为指向头结点,就使整个单链表形成一个环,这种头尾相接的单链表称为单循环链表,简称循环链表(circular linked)。
2、空循环链表仅由一个自成循环的头结点表示。
双 向 链 表
1、双向链表(double linked list)是在单链表的每个结点中,再设置一个指向其前驱结点的指针域。
- 第二章 线 性 表(顺序表、单链表、静态链表、循环链表、双向链表)
- 第二章(5).双向循环链表
- 数据结构——线性表 (顺序表、单链表、静态链表、循环链表、双向链表)
- 顺序表,单链表,单循环链表,双向循环链表
- 数据结构(第二天) 双向循环链表
- 双向循环链表
- 双向循环链表
- 双向循环链表
- 双向循环链表
- 双向循环链表
- 双向循环链表
- 双向循环链表
- 双向循环链表
- 双向循环链表
- 双向循环链表
- 双向循环链表
- 双向循环链表
- 双向循环链表
- 2014-10-15 好累
- 黑马程序员——高新技术---反射、内省、注解
- GPUImage
- Cocos2dx v3.xcpp-test编译生成apk包
- Ubuntu 1404关于Caps Lock与Ctrl交换问题解决
- 第二章 线 性 表(顺序表、单链表、静态链表、循环链表、双向链表)
- Cookie/Session机制详解
- IOS常用开源项目收藏
- web.xml中load-on-startup的作用
- 看男科万州哪好
- Android 图片拍照上传、本地上传
- Android 批量自动化打包、安装、运行的实现
- Android 实现从网络上异步加载图像
- DevEpxress设置SimpleButton按钮背景图片