第二章线性表线性结构特点:•唯一头元素•唯一尾元素•除头元素外,均有一个直接前驱•除尾元素外,均有一个直接后继书目信息、排队、算术表达式。
2.1 线性表的定义1. 线性表的语言定义线性表是n个数据元素的有限序列。
例,英文字母表(A,B,C,……,Z)线性表中的数据元素也可以由若干个数据项构成。
例,包含大量记录的登记表线性表可以表示为n 个数据元素的有限序列: (a1,…,a i-1,a i,…,a n)其中a1是头元素,a n是尾元素,a i是第i 个元素。
a i-1是a i的直接前驱,a i是a i-1的直接后继。
抽象数据类型线性表List 的定义:ADT List {数据对象: D = { a i | a i∈ElemSet,i = 1, 2, …, n }数据关系: R1 = { < a i-1, a i > }基本操作:InitList( &L )结果: 构造一个空的线性表L。
DestroyList( &L )条件: 线性表L 已存在。
…} ADT List其它基本操作包括:ClearList( &L )ListEmpty ( L )ListLength ( L )GetElem ( L,i,&e )LocateElem ( L,e,compare() )PriorElem ( L,cur_e,&pre_e )NextElem ( L,cur_e,&next_e )ListInsert ( &L,i,e )ListDelete ( &L,i,&e )ListTraverse ( L,visit() )2.2 线性表的顺序表示和实现线性表的顺序表示指的是用一组地址连续的存储单元依次存储线性表的数据元素。
线性表顺序存储结构表示# define LIST_INIT_SIZE 100# define LISTINCREMENT 10typedef struct {Elemtype* elem; //数据元素int length; // 表长,初始为0int listsize; // 表存储容量} SqList;Status InitList_Sq ( SqList &L ) { //初始化空线性表L.elem = ( ElemType * ) malloc( LIST_INIT_SIZE * sizeof(ElemType) );if ( ! L.elem ) exit(OVERFLOW) ;L.length = 0 ;L.listsize = LIST_INIT_SIZE ;return OK ;}线性表的顺序存储结构的优点•可随机存取表中任意数据元素(第i 个)L.elem[i-1]* (L.elem+i-1)•直接可获取线性表的长度L.length算法2.4 在第i个数据元素之前插入一个新的元素例,在第i 个元素前插入b思想:1. 找到第i个元素的位置。
2. 将第n到i个元素均向后移动一个位置。
3. 将新元素放置在第i个位置。
int ListInsert_Sq ( Sqlist &L ,int i ,ElemType e ) {ElemType *p, *q;q = & L.elem[i-1] ;//找到第i 个元素位置for ( p = & L.elem[L.length-1] ;p >= q ;p -- )* (p+1) = * p ;// 后移元素* q = e ;// 插入新元素++L.length ;return 0;}int ListDelete_Sq ( Sqlist &L ,int i ,ElemType &e ) {if ( i < 1 || i > L.length ) return 1 ;p = & L.elem[i-1] ;// 找到第i 个元素e= * p ;// 取第i 个元素的值for ( p++ ;p <= & L.elem[ L.length - 1];p ++ )* (p - 1) = * p ;// 前移-- L.length;return 0 ;}2.3 线性表的链式表示和实现线性表的链式存储结构的特点是用一组随意的存储单元存储线性表的数据元素。
例,线性表数据为{3,5,7}结点: 两部分信息组成,存储数据元素信息的数据域,存储直接后继存储位置信息的指针域。
线性表的单链表存储结构typedef struct LNode {ElemType data ;struct LNode * next ;} * LinkList ;2.3.1 线性单链表Head: 头指针,指向链表中第一个结点。
0: 空指针,有时也表示为“NULL”或“∧”。
头结点: 为了某些操作的方便,通常在链表第一个结点之前附加一个结点,没有实际意义。
空表:线性表的链式存储结构的特点缺点:•不可随机存取表中任意数据元素•不可直接获取线性表的长度算法2.8: 取出线性单链表第i个元素。
思想:1. 找到第1 个元素;2. 循环找到第i个元素;3. 取出元素;Status GetElem_L ( LinkList L,int i,ElemType &e ) { p = L->next ;j = 1 ;//p指向第一个元素,j计数while ( p && j < i ) {p = p->next ;++j ;//循环找到第i 个元素}if ( ! p || j > i ) return ERROR ;e = p->data ;return OK ;}例,取出第i=3个元素。
e = p->data = Sun平均时间复杂度: O(n)优点:数据元素的插入、删除相对方便在b之前插入元素x :1. 找到b结点的前驱结点2. 构造将要插入的结点3. 指针变换算法2.9 在第i个数据元素之前插入一个新的元素Status ListInsert_L ( LinkList &L ,int i ,ElemType e ) {1. 找到第i 个结点的前驱结点2. 构造将要插入的结点3. 指针变换}Status ListInsert_L ( LinkList &L ,int i ,ElemType e ) { p = L;j = 0;while ( p && j < i - 1 ) { p = p->next ;++j ;}//找到第i 个结点的前驱结点if ( ! p || j > i - 1 ) return ERROR ;s = ( LinkList ) malloc ( sizeof (LNode) ) ;s->data = e ;s->next = NULL ;//建立新结点s->next = p->next ;p->next = s ;//插入新结点return OK ;}例,在第3个元素之插入一个新元素。
p->next = ss->next = p->next平均时间复杂度: O(n)算法2.11 利用插入操作构造一条完整的单链表。
void CreateList_L ( LinkList &L,int n ) {L = ( LinkList ) malloc ( sizeof (LNode) ) ;L->next = NULL ;//建立头结点for ( i = n ;i > 0 ;--i ) {p = ( LinkList ) malloc ( sizeof (LNode) ) ;Scanf ( &p->data ) ;p->next = L->next ;L->next = p ;//在表头插入新结点}}例,讨论: 如何逆置一个单链表为一个新表?作业: 设计算法,将单链表L 中的第i 个结点和其后继结点交换位置,要求只修改指针。
删除元素b :1. 确定指针2. 取出要删除的结点3. 指针变换4. 释放内存1)p ->next = p ->next ->next2)q = p->nextp->next = q->nextfree(q) ;讨论单链表的删除操作,课堂演讲。
2.3.2 其他线性链表•循环链表•双向链表从某个结点出发寻找直接后继?从某个结点出发寻找直接前驱?1. 循环链表表中最后一个结点的指针域指向头结点,形成一个环。
优点:从表的任意结点出发均可以找到表中的任意其他结点。
空表:操作与线性单链表基本一致,差别只是在于算法中的循环结束条件不是p是否为空,而是p是否等于头指针。
例,取循环链表第i 个元素。
Status GetElem_L ( LinkList L,int i,ElemType &e ) { p = L->next ;j = 1 ;while ( p <> L && j < i ) {p = p->next ;++j ;}if ( p == L || j > i ) return ERROR ;e = p->data ;return OK ;}顺次合并两个线性表有时为了方便某些操作,通常在循环链表中设立尾指针。
Tail2->next = Tail1->nextp = Tail2->next ->nextTail2->next = Tail1->nextTail1->next = pp = Tail2->next ->nextq = Tail2->nextTail2->next = Tail1->nextTail1->next = pFree(q)在循环链表中寻找结点的直接后继很简单,只需要O(1);但要寻找结点的直接前趋需要循环一遍,需要O(n)。
2. 双向循环链表双向循环链表的结点有两个指针域: 一个指向直接后继,一个指向直接前趋。
typedef struct DuLNode{ElemType data ;struct DuLNode * prior ;struct DuLNode * next ;}DuLNode ,* DuLinkList ;空表:性质: 设d 是指向某个结点的指针,则有d->next->prior = d->prior->next = d操作: 插入、删除操作将会处理两个方向。