数据结构之线性表(List)
(本文为个人学习数据结构课程和三年磨一剑的<<大话数据结构>> 后的笔记,如有侵权,请直接联系我,立即删除)
一.线性表(List):零个或多个数据元素的有限序列.
若将线性表记为(a1,a2,...,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,..,n时,ai有且仅有一个直接前驱.
线性表的抽象数据类型定义如下:
ADT 线性表(List)
Data
线性表的数据对象集合为{a1,a2,...,an},每个数据元素的类型均为DataType.其中,除了第一个元素外,每个元素有且仅有一个前驱元素,除了最后一个元素外,每个元素有且
仅有一个直接后继元素.数据元素之间的关系是一对一的关系.
Operation
InitList(*L): 初始化操作,建立一个空的线性表L.
ListEmpty(L): 若线性表为空,返回true,否则返回false.
ClearList(*L): 将线性表清空.
GetElem(L,i,*e): 将线性表中的第i个位置元素返回给e.
LocateElem(L,e): 在线性表中查找与给定值e相等的元素,如果成功,返回true,否则返回false.
ListInsert(*L,i,e):在线性表的第i个位置插入新元素e.
ListDelete(*L,i,*e):删除线性表中的第i个元素,并用e返回其值.
ListLentgth(L) : 返回线性表的元素个数.
例:实现2个线性表集合A和B的并集操作
/*将所有的在Lb中但不在La中的数据元素插入到La中*/
void union(List *La, List *Lb)
{
int La_len, Lb_len, i;
ElemType e;
La_len = ListLentgth(La);
Lb_len = ListLentgth(Lb);
whlie(i < Lb_len )
{
GetElem(Lb,i,e);
if(!LocateElem(L,e))
ListInsert(La,++La_len,e);
}
}
二.线性表的顺序存储结构
定义:指用一段连续的存储单元依次存储线性表的数据元素.
#include<stdio.h> #define MAXSIZE 100 /*存储空间初始分配量*/ #define OK 1 #define ERROR 0 #define TURE 1 #define FALSE 0 typedef int ElemType, Status; /*ElemType 类型根据实际情况而定,这里假设为int*/ /*Status 是函数的类型,其值是函数结果的状态码,如OK等*/ /*线性表的顺序存储结构*/ typedef struct { ElemType data[MAXSIZE]; /*数组存储数据元素,线性表的最大容量为MANSIZE*/ int length; /*线性表当前长度*/ }SqList; /*初始化*/ void ListInit(SqList *L) { L->length = 0; //顺序表的初始化即将表的长度置为0 } /*求表长*/ Status ListLength(SqList *L) { return L->length; //求表长只需返回L->length } /*获得元素操作*/ /*Status 是函数的类型,其值是函数结果的状态码,如OK等*/ /*初始条件:顺序线性表已经存在,1 <= i <= ListLenght(L)*/ /*操作结果:用e返回L中第i个数据元素的值*/ // 在L中查找值为x 的结点,并返回该结点在L中的位置。 // 若L中有多个结点的值和x 相同,则返回首次找到的结点位置; // 若L中没有结点的值为x ,则返回一个特殊值表示查找失败。 Status GetElem(SqList *L, int i) { if(L->length == 0 || i> L->length) //当i不在范围之内 return ERROR; return L->data[i-1]; } /*插入元素操作*/ /*Status 是函数的类型,其值是函数结果的状态码,如OK等*/ /*初始条件:顺序线性表已经存在,1 <= i <= ListLenght(L)*/ /*操作结果:在L中第i个位置之前插入新的数据元素e,L的长度加1*/ // 在线性表L的第i个位置上插入一个值为x 的新结点, // 使得原编号为i,i+1,…,n的结点变为编号为i+1,i+2,…,n+1的结点。 // 这里1≤i≤n+1,而n是原表L的长度。插入后,表L的长度加1。 Status ListInsert(SqList *L, int i, ElemType e) { int k; if(L->length == MAXSIZE) //顺序线性表已经满 return ERROR; if(i<1 || i>L->length+1) //当i不在范围之内 return ERROR; if(i<= L->length) { for(k = L->length-1; k >= i-1; k--) { L->data[k+1] = L->data[k]; } } L->data[i-1] = e; //将新元素插入 L->length++; return OK; } /*删除元素操作*/ /*Status 是函数的类型,其值是函数结果的状态码,如OK等*/ /*初始条件:顺序线性表已经存在,1 <= i <= ListLenght(L)*/ /*操作结果:删除L中第i个数据元素,用e返回其值,L的长度减1*/ // 在线性表L的第i个位置上删除一个值为x 的结点, // 使得原编号为i,i+1,…,n的结点变为编号为i,i+1,…,n-1的结点。 // 这里1≤i≤n+1,而n是原表L的长度。删除后,表L的长度加1。 Status ListDelete(SqList *L, int i) { int k = 0; if(L->length==0) //顺序线性表为空 return ERROR; if(L->length == 0 || i> L->length) //当i不在范围之内 return ERROR; int e = L->data[i-1]; for(k=i;k<L->length;k++) { L->data[k-1] = L->data[k]; } L->length--; return e; } void print(SqList *L) { int i = 0; for(;i<L->length;i++) { printf(" %d ",L->data[i]); } } int main() { int x; //int a[] = {1,2,3,4,5}; SqList *L; ListInit(L); printf("线性表初始化后表长为:%d\n", ListLength(L)); printf("开始创建线性表,输入结点值,以\\结束\n"); while(scanf("%d",&x)==1) { ListInsert(L,1,x); } printf("向线性表插入数据元素后表长为:%d\n", ListLength(L)); printf("线性表数据元素为:"); print(L); printf("\n取线性表中第二个数据元素:%d\n", GetElem(L, 2)); ListDelete(L, 1); printf("在 线性表删除数据元素后表长为:%d\n", ListLength(L)); printf("线性表数据元素为:"); print(L); return 0; }
结果:
线性表顺序存储结构的优缺点:
优点 | 缺点 |
无须为表中的逻辑关系而增加额外的存储空间 可以快速地取到表中的任一位置的元素 |
插入和删除操作需要移动大量元素:O(n) 当线性表的长度变化较大时,难以确定存储空间的容量 造成存储空间的"碎片" |
三.线性表的链式存储结构
1.单链表
为了表示每个数据元素ai与其直接后继元素ai+1的逻辑关系,对ai来说,除了存储其自身的信息之外,还需存储一个指示其直接后继的信息(即直接后继的存储位置).我们
把存储数据元素的域称为数据域,把存储直接后继位置的域称为指针域.指针域中存储的信息称作指针或链.这两部分组成的数据元素ai的存储映像,称为结点(Node).
n个结点(ai的存储映像)链接成一个链表,即为线性表(a1,a2,...,an)的链式存储结构,因为此链表的每个结点中只包含一个指针域,所以叫单链表.
链表中第一个位置叫做头指针,在单链表的第一个结点前附设一个结点,称为头结点
头指针与头结点的异同:
头指针 | 头结点 |
头指针是指链表指向第一个结点的指针,若链表有头结点,则是指向头结点的指针 头指针具有标示作用,所以常用头指针冠以链表的名字 无论链表是否为空,头指针均不为空.头指针是链表的必要元素 |
头结点是为了操作的统一和方便而设立的,放在第一元素的结点之前,其数据域一般无意义(也可存放链表的长度) 有了头结点,对第一元素结点前插入结点和删除第一结点其操作与其他结点的操作就统一了 头结点不是链表的必须要素 |
#include<stdio.h> #include<malloc.h> #define OK 1 #define ERROR 0 #define TURE 1 #define FALSE 0 typedef int ElemType, Status; /*ElemType 类型根据实际情况而定,这里假设为int*/ /*Status 是函数的类型,其值是函数结果的状态码,如OK等*/ /*线性表的单链表存储结构*/ typedef struct Node { ElemType data; /*结点类型定义*/ struct Node *next; /*结点的指针域*/ }Node; typedef struct Node *LinkList; /*定义LinkList*/ /* 注意: ①LinkList和ListNode *是不同名字的同一个指针类型 (命名的不同是为了概念上更明确) ②LinkList类型的指针变量head表示它是单链表的头指针 ③ListNode *类型的指针变量p表示它是指向某一结点的指针 */ /*创建单链表*/ Status createList(LinkList *L) { int x; LinkList p; *L =(Node *)malloc( sizeof(Node) ); //头指针 (*L)->next = NULL; while(scanf("%d",&x)==1) { p = (Node *)malloc(sizeof(Node)); /*生成新结点*/ p->data=x; p->next = (*L)->next; (*L)->next = p; } return OK; } /*函数名称:print*/ /*功能描述:遍历单链表*/ /*返回类型:无返回值*/ /*函数参数:h:单链表头指针*/ void print(LinkList *L) { LinkList p; p=(*L)->next; while(p) { printf("%d ",p->data); /*输出p的数值域*/ p=p->next; /*p指向下一结点*/ } } /*函数名称:getLength*/ /*功能描述:获取单链表的长度*/ /*返回类型:无返回值*/ Status getLength(LinkList *L) { int i = 0; LinkList p; p=(*L)->next; while(p) { p=p->next; i++; } return i; } /*函数名称:ListInsert*/ /*功能描述:向单链表第i个插入新的结点*/ /*返回类型:1*/ Status ListInsert(LinkList *L, int i, ElemType e) { int j=1; LinkList p,s; p=*L; while(p&&j<i) /*寻找第i个结点*/ { p=p->next; j++; } if(!p || j>i) /*第i个元素不存在*/ return ERROR; s = (Node *)malloc(sizeof(Node)); /*生成新结点*/ s->data = e; s->next = p->next; /*将p的后继结点赋值给s的后继*/ p->next = s; /*将s赋值给p的后继*/ return OK; } /*函数名称:ListInsert*/ /*功能描述:删除单链表第i个结点*/ /*返回类型:1*/ Status ListDelete(LinkList *L, int i) { int j=1, e; LinkList p,q; p=*L; while(p->next&&j<i) /*寻找第i个结点*/ { p=p->next; j++; } if(!p || j>i) /*第i个元素不存在*/ return ERROR; e = p->data; q = p->next; p->next = q->next; free(q); /*系统回收此结点,释放内存*/ return OK; } /*函数名称:ListInsert*/ /*功能描述:清空整个单链表*/ /*返回类型:1*/ Status clearList(LinkList *L) { LinkList p,q; p=(*L)->next; /*p指向第一个结点*/ while(p) { q=p->next; free(q); /*系统回收此结点,释放内存*/ p=q; } (*L)->next = NULL; /*头结点的指针域置空*/ return OK; } int main() { LinkList *L; printf("开始创建链表,输入结点值,以\\结束\n"); createList(L); printf("\n单链表的长度为:%d \n", getLength(L)); printf("\n输出单链表:"); print(L); printf("\n向单链表中插入元素结点:"); ListInsert(L, 2, 111); printf("\n输出单链表:"); print(L); printf("\n删除单链表中该已经插入的结点:"); ListDelete(L, 2); printf("\n输出单链表:"); print(L); return 0; }
运行结果:
单链表的合并
void MergeList_L(LinkList &La,LinkList &Lb,LinkList &Lc) { pa= La->next ; pb=Lb->next; Lc=pc=La; while ( pa!=NULL && pb!=NULL ) { if (pa->data<=pb->data) { pc->next = pa; pa = pa->next; pc = pc->next; } else { pc->next=pb; pb=pb->next; pc=pc->next; } } if(pa==NULL) pc->next = pb; if(pb==NULL) pc->next=pa; free(Lb); }
单链表结构与顺序存储结构优缺点:
存储分配方式 | 时间性能 | 空间性能 |
顺序存储结构用一段连续的存储单元依次存储线性表的元素 单链表采用链式存储结构,用一组任意的存储单元存放线性表的元素 |
查找 顺序存储结构O(1) 单链表O(n) 插入和删除 顺序结构需要平均移动表长的一半的元素,时间为O(n) 单链表在找到某位置的指针后,插入和删除时间仅为O(1) |
顺序存储结构需要预先分配存储空间 单链表不需要分配存储空间,元素个数也不受限制 |
2.静态链表
定义:用数组描述的链表
3.循环链表
将单链表中终端结点的指针由空指针改为指向头结点,就使整个单链表形成一个环,这种头尾相接的单链表成为单循环链表,简称循环链表(Circular linked list)
将两个带头节点的循环单链表合并成一个循环单链表
Linklist Union_L(LinkList &r1,LinkList &r2) { q=r2->next; r2->next=r1->next; r1->next=q->next; free(q); return r2; }
4.双向链表(double linked list)
定义:在单链表的每个结点中,再设置一个指向其前驱结点的指针域
#include<stdio.h> #include<malloc.h> #define OK 1 #define ERROR 0 #define TURE 1 #define FALSE 0 typedef int ElemType, Status; /*ElemType 类型根据实际情况而定,这里假设为int*/ /*Status 是函数的类型,其值是函数结果的状态码,如OK等*/ /*线性表的单链表存储结构*/ typedef struct DulNode { ElemType data; /*结点类型定义*/ struct DulNode *prior; /*结点的直接前驱指针*/ struct DulNode *next; /*结点的直接后继指针*/ }DulNode, *DulLinkList; /*定义DulLinkList*/ /*创建双向链表*/ Status createList(DulLinkList *L) { int x=6; DulLinkList s; *L =(DulNode *)malloc( sizeof(DulNode) ); // 创建头结点 (*L)->data = 0; (*L)->prior = NULL; (*L)->next = NULL; while(x>=0) { s = (DulNode *)malloc(sizeof(DulNode)); /*生成新结点*/ s->data=x; s->prior = (*L); s->next = (*L)->next; if((*L)->next) (*L)->next->prior = s; (*L)->next = s; x--; } return OK; } /*函数名称:print*/ /*功能描述:遍历单链表*/ /*返回类型:无返回值*/ /*函数参数:h:单链表头指针*/ void print(DulLinkList *L) { DulLinkList p; p=(*L)->next; while(p) { printf("%d ",p->data); /*输出p的数值域*/ p=p->next; /*p指向下一结点*/ } } /*函数名称:getLength*/ /*功能描述:获取单链表的长度*/ /*返回类型:无返回值*/ Status getLength(DulLinkList *L) { int i = 0; DulLinkList p; p=(*L)->next; while(p) { p=p->next; i++; } return i; } /*函数名称:ListInsert*/ /*功能描述:向双向单链表第i个插入新的结点*/ /*返回类型:1*/ Status ListInsert(DulLinkList *L, int i, ElemType e) { int j=1; DulLinkList p,s; p=*L; while(p&&j<i) /*寻找第i个结点*/ { p=p->next; j++; } if(!p || j>i) /*第i个元素不存在*/ return ERROR; s = (DulNode *)malloc(sizeof(DulNode)); /*生成新结点*/ s->data = e; s->prior = p; s->next = p->next; p->next->prior = s; p->next = s; return OK; } /*函数名称:ListInsert*/ /*功能描述:删除双向单链表第i个结点*/ /*返回类型:1*/ Status ListDelete(DulLinkList *L, int i) { int j=1, e; DulLinkList p,q; p=*L; while(p->next&&j<i) /*寻找第i个结点*/ { p=p->next; j++; } if(!p || j>i) /*第i个元素不存在*/ return ERROR; e = p->data; q = p->next; q->next->prior = p; p->next = q->next; free(q); /*系统回收此结点,释放内存*/ return OK; } int main() { DulLinkList *L; printf("开始创建双向链表\n"); createList(L); printf("双向链表的长度为:%d \n", getLength(L)); printf("输出双向链表:"); print(L); printf("\n向双向链表中插入元素结点:"); ListInsert(L, 1, 111); printf("\n输出双向链表:"); print(L); printf("\n删除单链表中该已经插入的结点:"); ListDelete(L, 1); printf("\n输出单链表:"); print(L); return 0; }
运行结果: