什么是线形表?
答:一种数据结构,简单的说, N 个数据元素的有限序列。
线性结构的特点:
① 只有一个首结点和尾结点;
② 除首尾结点外,其他结点只有一个直接前驱和一个直接后继。
线性表顺序存储特点:
① 逻辑上相邻的数据元素,其物理上也相邻;
② 若已知表中首元素在存储器中的位置,则其他元素存放位置亦可求出(利用数组下标)。
例 1 :一个一维数组M,下标的范围是0到9,每个数组元素用相邻的5个字节存储。存储器按字节编址,设存储数组元素 M[ 0]的第一个字节的地址是98,则M [ 3 ] 的第一个字节的地是 ______
解: 地址计算通式为
LOC(ai) = LOC(a1) + L * ( i-1 )
因此: LOC( M[3] ) = 98 + 5 × (3-0) =113
线形表的实现:
定义该数据结构:
#define LIST_INIT_SIZE 100 // 初始化分配量, 100 个元素为默认的 线性表的容量
#define LISTINCREMENT 10 // 线性表存储空间的分配增量
Typedef struct {
Elemtype *elem ; // 定义一个指针,用于存放线性表的基址
Int length ; // 定义线性表的长度
Int listsize ;}sqlist // 当前线性表分配到的容量
创建一个线性表:即初始化一个线性表,构造一个空的线性表
Status initList_sq (sqlist &L) {
L.elem = (ElemType *) malloc (LIST_INIT_SIZE*sizeof(ElemType))
If (!L.elem) exit(OVERFOLOW);
L.lengh = 0 ;
L.listsize = LIST_INIT_SIZE ;
Return OK }
顺序表的插入操作(重要,必须倒背如流)
思路:
① 插入操作应该必须知道些什么呢?
答:插到哪个线性表,和插到该线性表的什么位置,还有插的元素,所以该插入函数有三个参数 &L , int I ,elemtype e .
② 插入操作中会有什么可能出现的错误呢?
答 A: 插入的位置应该是逻辑上成立的 1 =< I <= length(L)+1]
B: 如果线性表已经装满了,应该分配新的空间
Length>=listsize 时候说明满了
Newbase = (elemtype *) realloc (L.elem,
( L.listsize + LISTINCREMENT ) *sizeof(elemtype));
③ 插入后,会对别的元素造成什么样的影响?
答 如果插入的位置是 I ,那么 从 I ->length(L) 都将被移动,共需移动元素 N-I+1 很明显,应该从最右边的元素下手,依次将 I -> n 的元素右移。 P 是指向最后一个元素的指针, *(P +1)= *P , 最后应该完成扫尾工作 ++length .
下面写出原码 :
Void List_insert_sq(&L , int I ,elemtype e .) {
if (i<1 || i>length(L)+1); return Error; // 插入的位置应该是逻辑上成立
if (L.length >= L.listsize ) { // 如果线性表已经装满了,应该分配新的空间
Newbase = (elemtype *) realloc (L.elem,
(L .listsize + LISTINCREMENT ) *sizeof(elemtype));
If (!newbase = 0 ) exit(OVERFLOW)
L.elem = Newbase;
L.Listsize = .listsize + LISTINCREMENT ; }
Q = &(L.elem[i-1]); // 插入的位置
For (P = &(L.elem[L.length-1]) ; P>=Q; P--) {
*(P +1)= *P ; } // 依次将 I -> n 的元素右移
*P = e ;
++L.Length ; }
分析上程序的时间复杂度
最坏的情况下是: 插入的位置是 1 ,将移动 N-I+1 个元素,插入的位置的概率是 1/n+1, 所以平均算法时间复杂读是 O(N/2) ;
顺序表的删除操作(重要,必须倒背如流)
思路:
1 删除操作应该必须知道些什么呢?
答:删除哪个线性表,和删除该线性表的什么位置,返回删除元素的值,所以该插入函数有三个参数 &L , int I ,elemtype e .
2 删除操作会出现什么可能的错误?
答:删除的元素应该在线性表内 1=< I <= length
3 删除后会对周围元素造成什么样的影响?
答:删除的位置如果是 I ,那么 i+1 到 N 的元素都将会左移一位 * ( P-1 ) =*P
很明显,应该把从线性表的左边移动,此前把 *P 赋给 e , 最后扫尾 –Length
下面给出原码:
Status list_delete_sq (&L , int I ,elemtype e .) {
If (i<1 || i>length(L)) then return error ;
p = L.elem[i-1] ;
E = *p;
Q = L.elem + L.length-1;
For (p; p<=q ; p++)
*( P-1 )=* P ;
---L.length ; }
分析上程序的时间复杂度
最坏的情况下是: 删除的位置是 1 ,将移动 N-I 个元素,插入的位置的概率是 1/n+1, 所以平均算法时间复杂读是 O((N-1)/2) ;
关于 MALLOC 与 REALLOC 的理解
首先看一下下面的 C 程序片断:
Elemtype malloc(size) // Elemtype 指的是数据类型,比如 INT ,那就意味着一个指针指示 4 个字节
Elemtype realloc( 原指针, SIZE) ,即一个元素是 4 个字节。
#include <malloc.h>
char *p;
p = (char * ) malloc (10);
p = (char * ) realloc (p,20);
函数首先定义了一个字符型的指针 p ,然后为指针 p 分配了一个 10 个字节大小的内存空间,接着将这个内存块的大小增加到 20 个字节。关于 realloc 究竟在哪里分配的问题,完全由实现标准库的程序决定。它可能在原来的地方扩充,也有可能全部另行分配(想想,如果现在要扩大存储块,而后面的地方已经被分配出去另有他用了,你怎么可能在原位扩充呢?)。具体情况我们不必考虑,从抽象的层次上理解其功能就够了。
顺序表的定位操作:(巧妙的设计)
思路:要求查找线性表中是否有与 e 相同的元素,如果有则返回该元素的序号,否则返回 0
1. 定位过程必须知道什么?
答: 对哪个表进行定位,返回与哪个数相等的序列号。所以需要两个参数
2. 怎么进行比较控制?
答: 须对线性表中的元素都进行比较,所以应该知道线性表的长度,用一个循环依次取出每个元素比较。找到回返回序号。否则返回 0
Int locate_list-sq (&L,e) {
For (I = 1 ; i<=L.length&&!(e==L.elem[i-1]);++i) {}
If (I<=L.length) then reture I
Else return 0 ; }
该算法的巧妙之处在于 巧妙的利用了 ++i,意思是在 L 查找不到相等的元素后跳出循环, I 已经先加上 1 了,所以 I>L.length, 返回的值是 0 。
分析上程序的时间复杂度
最坏的情况下是:找到符合条件的元素在最后一个,所以时间复杂度是 O ( L.Length )
线性表之间的操作:
例 1 :线性表 LA 与 LB ,求 A=AUB ;
思路:求两线性表的并,注意集合的定义,没有相同的元素!,一个循环解决问题
Void union(LA,LB) {
For (I = 1, i<=LB.length;i++){
Getelem(LB,I,e);
If (!locateelem(LA,e));
Listinsert(LA,++LA.length+1,e) }
例 2 :线性表 LA 与 LB 中元素是按非递减有序排列,求 LC=LAULB ;
思路:初始化一个线性表 LC ,将 LA 与 LB 中元素比较,小的进 LC 。最后再扫尾
Void mergelist(LA,LB,LC) {
Init(LC);
I=j=k=1 ;
While (i<=la.length && j<=lb.length)
{ Getelem(la,I,ai) ; Getelem(la,j,ai) ;}
If (ai<=bi) {listinsert(LC, k++,ai) ; i++}
Else {listinsert(LC,++k++,bi) ; J++}
While(i<=la.length) {getelem(la,i++,ai) ;listinsert(LC, k++,ai);
While(j<=lb.length) {getelem(lb,j++,bi) ;listinsert(LC, k++,bi);
线性表的链式表示和实现
链式存储特点:
答: 逻辑上相邻,物理上不一定相邻
在单链表中,除了首元结点外,任一结点的存储位置由其直接前驱结点的链域的值指示
头结点 是在链表的首元结点之前附设的一个结点;数据域内只放空表标志和表长等信息 ;
首元结点 是指链表中存储线性表第一个数据元素 a1 的结点。
单链表是非随机访问的储存结构,它的 I 结点的存储位置包含在 i-1 个结点的信息中。
对单链表的访问操作 GETELEM
怎么访问 LINKLIST 中任一指定的结点?
答:与顺序表不同,顺序表只需要知道线性表的基址,再加上偏移量就可以得出。而链表的物理存储位置可能是不相邻的,因此我们必须从头结点开始,才可以找出某结点的存储位置。
那怎么控制呢?
答:如果访问的是第 5 个元素,那我们只需要找到 5-1=4 个元素,就可以访问它了。我们可以设置一个计数器控制循环,一直访问到 i-1 个元素。
怎么控制查询范围的错误呢?
答:如果 i-1 个元素的指针域为空,那就说明 i-1 是链表的最后一个结点,没有 I 结点,返回错误。如果查询的是 <1 的结点,返回错误。
下面给出算法:
Status Getelem(linklist L, int I, elemtype e) }
P=L->next ; j=1;
While ( p && j < I ) {
P = p->next ;
++J }
If ( !p && j > i) returnerror ;
E = p->data ;
Return OK; }
单链表的插入操作:
思路: 在顺序链表中,在 I 前插入一个元素,只需要将 N-I+1 个元素都向右移动一位首先将指针移动到表尾元素 *(p+1)=*p . 但是链表在物理上的存储空间是不相邻的,第 I 个元素的物理位置是保存在第 I-1 个元素的指针域里,所以,首先,我们必须找到第 I-1 个元素,也就是我们需要得到第 I-1 个元素的指针。
怎么能够得到第 I-1 个元素的指针呢?
答: L 是头指针,指向头结点。如果我们想得到指向 I-1 个元素的指针,我们应该将 L 移动 I - 1 次。所以应该注意,循环 I-1 次 。
如果我们想要插入的位置不在 1 <=i<=n+1 呢?
答:如果插入的位置超出表长,怎么判断超出了表长?如果一个链表有 6 个元素,那么从 1~7 的插入位置都是合理的,如果插入的位置是 7 ,那么 I-1 个元素的既 6 的指针是存放在第 5 个元素里的。所以 I-1 元素的指针不为空。那么如果刚刚超出表长一位,要求插入的位置是 8 ,那么 I-1 个元素即 7 的指针是存放在 6 里面的,而表长是 6 ,所以 6 的指针为空。 由此我们得出,看是否超出表长,只需要判断 I-1 个元素的指针是否为空。其次,插入位置当然不能小于 1 。
下面是源码:
Status listinsert_link (linklist L, int I , elemtype e) {
P = L ; j = 0 ;
While( p && j < i-1 ) {p=p->next , ++j}
If (!P && j > i-1) returnerror ;
S = (linklist) malloc (sizeof(Lnode)) ;
s->next = p-.next ;
p-next = s ;
s->data = e ;
return OK ; }