( 以下内容全部来自《大话数据结构》 )
线性表:
零个或多个数据元素的有限序列。
若将线性表记为 (a1,...,ai-1,ai,ai+1,...,an ),则表中 ai-1 领先于 ai,ai 领先于 ai+1, 称 ai-1 是 ai 的直接前驱元素, ai+1 是 ai 的直接后区元素。当 i= 1, 2, 3, ... , n-1 时, ai 有且仅有一个直接后继,当 i= 2, 3, ... , n 时,ai 有且仅有一个直接前驱。
线性表元素的个数 n(n>=0) 定义为线性表的长度,当 n=0 时,称谓空表
线性表的抽象数据模型
线性表的抽象数据类型定义如下:
ADT 线性表 (List) Data 线性表的数据对象集合为 { a1,a2 ,...,an },每个元素的类型均为 DataType。 其中,除了第一个元素 a1 外有且只有一个直接前驱元 素,除了随后一个元素 an 外,每一个元素有且只有一个直接后继元素。数据元素之间的关系是一对一的关系。 Operation InitList ( *L ) : 初始化操作,建立一个空的线性表 L 。 ListEmpty( L ) : 若线性表为空,返回 true, 否则返回 false 。 ClearList ( *L ): 将线性表清空。 GetElem ( L, i, *e ): 将线性表 L 中的 第 i 个位置元素值返回给 e 。 LocateElem ( L, e ): 在线性表 L 中查找与给定值 e 相等的元素,如果查找成功,返回该元素在表中序号表示成功;否则,返回 0 标示失败。 ListInsert ( *L, i, e ): 在线性表 L 中的第 i 个位置插入新元素 e 。 ListDelete ( *L, i, *e ): 删除线性表 L 中第 i 个位置元素, 并用 e 返回其值。 ListLength ( L ): 返回线性表 L 的元素个数 endADT |
合并两个集合
/* 将所有的在线性表 Lb 中但不在La 中的数据元素插入到 La 中 */ void union ( List *La, List Lb ) { int La_Len, Lb_len, i ; ElemType e; /* 声明 La 和 Lb 相同的数据元素 e */ La_len = ListLength ( La ); /* 求线性表的长度 */ Lb_len = ListLength ( Lb ); for( i = 1 ; i <= Lb_len ; i++ ) { GetElem ( Lb, i, e ); /* 取 Lb 中第 i 个数据元素赋给 e */ if ( !LocateElem ( La, e, equal ) ) /* La 中不存在和 e 相同的数据元素 */ { ListInsert ( La, ++ La_len, e ); /* 插入 */ } } } |
线性表的顺序存储结构
顺序存储定义
线性表的顺序存储结构, 指的是用一段地址连续的存储单元依次存储线性表的数据元素。
顺序存储方式(用的是一维数组来实现顺序存储结构)
线性表的顺序存储的结构代码。
#define MAXSIZE 20 /* 存储空间初始分配量 */ typedef int ElemType ; /* ElemType 类型根据实际情况而定 */ typedef struct { ElemType data [ MAXSIZE ] /* 数组存储数据元素,最大值为 MAXSIZE */; int length ; /* 线性表当前长度 */ }sqlList ; |
顺序存储结构需要三个属性:
数据长度与线性表长度的区别:
在任意时刻,线性表的长度应该小于等于数组的长度。
地址计算方法
存储器中的每个存储单元都有自己的编号,这个编号称谓地址。
(LOC 表示获得存储位置的函数)
一个元素占有 c 个存储单元。
对于第 i 个数据元素 的存储位置可以由 推算得出:
LOC( ai ) = LOC(a1)+ (i - 1)* c
顺序存储结构的插入与删除
获得元素的操作
代码:
#define OK 1 #define ERROR 0 #define TRUE 1 #define FALSE 0 #typedef int Status ; /* Status 是函数的类型,其值是函数结果状态代码, 如 OK 等 */ /* 初始条件:顺序线性表 L 已经存在, */ /* 操作结果:用 e 返回 L 中第 i 个数据元素的值 */ Status GetElem ( SqlList L , int i , ElemType *e ) { if ( L.length ==0 || i < 1 || i > L.length ) { return ERROR; } *e = L.data[ i-1 ] ; return OK; } |
插入操作
插入算法的思路:
/* 初始条件:顺序线性表 L 已存在, 1<=i<=ListLength( L ) */ /* 操作结果:在 L 中第 i 个位置 */ Status ListInsert ( SqList *L, int i, ElemType e ) { int k ; if( L->length == MAXSIZE ) /* 顺序线性表已经满了 */ { return ERROR; } if ( i<1 || i>-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; } |
删除操作:
删除算法的思路:
实现代码
/* 初始条件:顺序线性表 L 已经存在, 1 <= i <= ListLength (L) */ /* 操作结果:删除 L 的第 i 个数据元素,并用 e 返回其值,L 的长度减 1 */ Status ListDelete ( SqlList *L, int i , ElempType *e ) { int k ; if (L->length==0) /* 线性表为空 */ { return ERROR; } if ( i < 1 || i>L-length ) /* 删除位置不正确 */ { return ERROR; } *e = L->data[i-1]; if ( i { for ( k = i ; k < L->length ; k++ ) /* 将删除位置后继元素后移 */ { L->data[k-1] = L-data[k]; } } L->length--; return OK; } |
线性列表的优点和缺点
优点 |
缺点 |
1. 无须为表示表中元素之间的逻辑关系而增加额外的存储空间。 2. 可以快速的存取表中任一位置的元素。 |
1. 插入和删除操作需要移动大量元素。 2. 当线性表长度变化较大时,难以确定存储空间的容量。 3. 造成存储空间的碎片 |
线性表的链式存储结构。
为了表示每个数据元素 ai 与其直接后继数据元素 ai+1 之间的逻辑关系,对数据元素 ai 来说,除了存储其本身的信息之外,还需存储一个指示其直接后继的信息(即直接后继的存储位置)。我们把存储数据元素信息的域成为数据域,把存储直接后继位置的域称为指针域。指针域中存储的信息称作指针或链。这两部分信息组成数据元素 ai 的存储映像,称为结点(Node)。
n 个结点(ai 的存储影像)链结成一个链表,即为线性表(a1 ,a2 ,...,an )的链式存储结构,因为此链表的每个结点中只包含一个指针域,所以叫单链表。
把链表中的第一个结点的存储位置叫做头指针。
在单链表的第一个结点前辐射一个结点,称为头结点。(头结点的数据域可以不存储任何信息,也可以存储如线性表的长度等附加信息,头结点的指针域存储指向第一个结点的指针)。
头指针与头结点的异同
头指针 |
头结点 |
1. 头指针是指链表指向第一个几点的指针,若链表有头结点,则是指向头结点的指针。 2. 头指针具有标示作用,所以常用头指针冠以链表的名字 3. 无论链表是否为空,头指针均不为空。头指针是链表的必要元素。 |
1. 头结点是为了操作的统一和方便而设立的,放在第一个元素的结点之前,其数据域一般无意义(也可以存放链表的长度)。 2. 有了头结点,对在第一元素借点钱插入结点和删除第一结点,其操作与其他结点的操作就统一了。 3. 头结点不一定是链表必须要素。 |
线性表链式存储结构代码描述
单链表中我们可用结构指针来描述
/* 线性表的单链表存储结构 */ typedef struct Node { ElemType data ; struct Node *next ; } Node; typedef struct Node *LinkList ; /* 定义 LinkList */ |
结点有存放数据元素的数据域存放后继结点地址的指针域组成 。
单链表的读取:
获取链表第 i 个数据的算法思路:
/* 初始条件:顺序链表 L 已经存在, 1 <= i <= ListLength( L ) */ /* 操作结果:用 e 返回 L 中第 i 个数据元素的值 */ |
Status GetElem ( LinkList L, int i , ElemType *e )
{
int j ;
LinkList p; /* 声明一个结点 P */
p = L->next ; /* 让 p 指向链表 L 的第一个结点 */
j = 1 ; /* j 为计数器 */
while( p && j /* p 不为空或者计数器 j 还没有等于 i 时 ,循环继续 */
{
p = p->next ; /* 让 p 指向下一个结点 */
++j ;
}
if( !p || j > i )
{
return ERROR; /* 第 i 个元素不存在 */
}
*e = p->data; /* 取出来第 i 个元素的数据 */
return OK;
}
(主要核心思想就是“工作指针的后移”;)单链表的插入与删除
单链表的插入思路
/* 初始条件:顺序线性表 L 已经存在, i <= i <= ListLength( L ) */ /* 操作结果:在 L 中第 i 个位置之前插入新的数据元素 e, L 的长度加 1 */ Status ListInsert ( LinkList *L , int i, ElemType e ) { int j ; LinkList p, s ; p = * L ; j = 1 ; while ( p && j < i ) /* 寻找第 i 个结点 */ { p = p->next; ++j; } if ( !p || j > i ) { return ERROR; /* 第 i 个元素不存在 */ } s = ( LinkList ) malloc ( sizeof( Node ) ); /* 生成新节点(C标准函数) */ s->data = e ; s->next = p->next ; /* 将 p 的后继结点赋值给 s 的后继 */ p->next = s ; /* 将 s 赋值给 p 的后继 */ return OK ; } |
单链表的删除思路
实现代码算法如下:
/* 初始条件:顺序线性表L已存在, 1 <= i <= ListLength( L ) */ /* 操作结果:删除 L 的第 i 个数据元素, 并用 e 返回其值,L 的长度减 1 */ Status ListDelete ( LinkList *L , int i, ElemType *e ) { int j ; LinkList p , q ; p = *L ; j = 1 ; while ( p->next && j < i ) /* 遍历寻找第 i 个元素 */ { p = p->next ; ++ j ; } if ( !( p->next ) || j > i ) { return ERROR ; /* 第 i 个元素不存在 */ } q = p->next ; p->next = q->next ; /* 将 q 的后继赋值给 p 的后继 */ * q = q->data ; /* 将 q 结点中的数据给 e */ free ( q ) ; /* 让系统回收此结点,释放内存 */ return OK ; } |
对于插入或者删除数据越频繁的操作,单链表的效率优势就越是明显。
单链表的整表创建的算法思路:
实现代码:
/* 随机产生 n 个元素的值,建立带表头结点的单链线性表 L ( 头插法 ) */ void CreateListHead ( LinkList *L , int n ) { LinkList p ; int i ; srand ( tiime (0) ); /* 初始化随机数种子 */ *L = ( LinkList ) malloc ( sizeof(Node) ); (*L)->next j= NULL ; /* 先建立一个带头结点的单链表 */ for ( i = 0; i { p = ( LinkList ) malloc ( sizeof( Node ) ) ; /* 生成新节点 */ p->data = rand() % 100 + 1 ; /* 随机生成 100 以内的数字 */ p->next = (*L)->next ; (*L) ->next = p ; /* 插入到表头 */ } } |
/* 随机生成 n 个元素的值,建立带表头结点的单链表 L(尾插法) */ { LinkList p, r ; int i ; srand ( time(0) ); /* 初始化随机数种子 */ *L = ( LinkList ) malloc ( sizeof( Node ) ); /* 为整个线性表 */ r = *L ; /* r 为指向尾部的结点 */ for ( i = 0 ; i < n ; i++ ) { p = ( Node * ) malloc ( sizeof(Node) ); /* 生成新结点 */ p->data = rand() % 100 + 1 ; /* 随机生成 100 以内的数字 */ r->next = p ; /* 将表尾终端结点的指针指向新结点 */ r = p; /* 将当前的新结点定义为表尾终端结点 */ } r-next = NULL; /* 表示当前链表结束 */ } |
实现算法:
/* 初始条件 : 顺序线性表 L 已存在,操作结果 : 将 L 重置为空表 */ Status ClearList ( LinkList *L ) { LinkList p, q ; p = ( *L )->next; while ( p ) { q = p -> next ; free ( p ) ; p = q ; } ( *L )->next = NULL ; return OK; } |