【学习笔记】数据结构与算法第二章 线性表

#我的编程语言学习笔记#

目录

一、线性结构定义

线性结构的特点:

线性表linear list:

定义:

逻辑结构:

ADT定义:

ADT应用:

1、集合合并A=AUB

2、有序列表合并

3、比较

二、线性表的顺序表示和实现

表示

实现

1、初始化:O(1)

2、元素插入:O(ListLength(L)) → O(n)

3、元素删除:O(n)

比较插入和删除

4、查找元素:O(L.length)

5、合并“元素赋值”:O(La.length+Lb.length)

三、线性表的链式表示和实现

表示

链式存储

四、单链表

(1)C指针实现

结点的类型定义

结点的赋值

基本操作的实现

1、单链表的查找:O(n)

2、单链表的插入:O(n)

3、单链表结点的删除:O(n)

4、创建单链表:O(ListLength(L))

5、单链表的合并:O(m+n)

6、应用:一元多项式

(2)C数组实现:静态链表

定义

基本操作的实现

1、初始化

2、创建一个静态链表

3、分配和释放

4、插入结点

5、删除结点

6、查找结点

7、应用:集合合并(只保留不相同的元素)

五、双向链表Double Linked List

 定义:

基本操作的实现:

1、创建

2、查找e

3、查找i

4、插入

5、删除

六、循环链表Circular Linked List

单链循环链表

双向循环链表

七、链式存储方式的比较


一、线性结构定义

线性结构的特点:

  • 存在一个唯一的被称为“第一个”的数据元素
  • 存在一个唯一的被称为“最后一个”的数据元素
  • 除第一个元素外,每个元素均有唯一一个直接前驱 (Predecessor)
  • 除最后一个元素外,每个元素均有唯一一个直接后继 (Successor)

线性表linear list:

定义:

由n(n≧0)个数据元素(结点)a 1 ,a 2 , …a n 组成的有限序列,且该序列中的所有结点具有相同的据类型
  • 线性表长度:数据元素的个数n
  • n=0:空表
  • n>0:记作(a1,a2,…an)
    • a1称为线性表的第一个(首)结点
    • an称为线性表的最后一个(尾)结点
    • a1,a2,…ai-1都是ai (2≦i≦n)的前驱,其中ai-1是ai的直接前驱
    • ai+1,ai+2,…an都是ai (1≦ i ≦n-1)的后继,其中ai+1是ai的直接后继
  • 若线性表中的结点是按值(或按关键字值)由小到大(或由大到小)排列的,称线性表是有序的

逻辑结构

线性表中的结点:

  • 单值元素
    • 每个元素只有一个数据项
  • 记录型元素
    • 每个记录含有多个数据项,每个项称为结点的一个域
    • 每个元素有一个可以唯一标识每个结点的数据项组,称为关键字

ADT定义:

ADT List{
数据对象 :D = { a i | a i ∈ElemSet, i=1,2,…,n, n≧0 }
数据关系 :R = {<a i-1 , a i > | a i-1 , a i ∈D, i=2,3,…,n }
基本操作
        InitList( &L )  //初始化
        操作结果:构造一个空的线性表L
        DestroyList(&L) //销毁操作
        初始条件:线性表L已存在
        操作结果:销毁线性表L
        ListEmpty(L) //线性表判空
        初始条件:线性表L已存在
        操作结果:若L为空表,则返回TRUE,否则返回FALSE
        
        ListLength( L ) //求线性表长度
        初始条件:线性表L已存在
        操作结果:返回L中数据元素个数
        GetElem( L, i, &e ) //求某个数据元素
        初始条件:线性表L已存在,1≦i≦ListLength(L)
        操作结果:用e返回L中第i个数据元素的值
        LocateElem(L, e, compare) //定位数据元素
        初始条件:线性表L已存在, compare() 是数据元素判定函数
        操作结果:返回L中第一个与e满足关系compare()的数据元素的位置,若这样的数据元                            素不存在,则返回值为0
        
        PriorElem(L, cur_e, &pre_e) //求前驱
        初始条件:线性表L已存在
        操作结果:若cur_e是L的数据元素,且不是第一个,则用pre_e返回它的前驱,否则操                            作失败,pre_e无定义
        NextElem(L, cur_e, &next_e) //求后继
        初始条件:线性表L已存在
        操作结果:若cur_e是L的数据元素,且不是最后一个,则用next_e返回它的后继,否则                            操作失败,next_e无定义
        ListTraverse(L, visit) //遍历线性表
        初始条件:线性表L已存在
        操作结果:依次对L的每个数据元素调用函数 visit() 。一旦visit()失败,则操作失败
        ClearList(&L) //将表置空
        初始条件:线性表L已存在
        操作结果:将L重置为空表
        ListInsert ( &L, i, e ) //插入数据元素
        初始条件:线性表L已存在,1≦i≦ListLength(L) +1;
        操作结果:在线性表L中的第i个位置之前插入元素e,L的长度加1;
        ListDelete(&L,i,&e) //删除数据元素
        初始条件:线性表L已存在且非空,1≦i≦ListLength(L)
        操作结果:删除L的第i个数据元素,并用e返回其值,L的长度减1
        
        } ADT List

ADT应用:

1、集合合并A=AUB
依次查看线性表 LB的每个数据元素
GetElem(LB, i, &e)
根据元素值在线性表 LA 中进行查找
LocateElem(LA, e, equal)
若不存在,则插入之
ListInsert(&LA, n+1, e), n为LA的当前长度
void union(List &La, List Lb){
    int La_len, Lb_len, i;
    ElemType e;
    La_len = ListLength(La);
    Lb_len = ListLength(Lb);
    for(i=1; i<=Lb_len; i++){
        GetElem(Lb, i, &e);
        if(!LocateElem(La, e, equeal)
            ListInsert(La, ++La_len, e);
    }
}
2、有序列表合并
功能:合并两有序列表(其值按非递减顺序排列)成一新有序列表
1. 初始化 Lc 为空表;
2. 分别从 La和Lb中取得当前元素 ai 和 b j
3. (La、Lb不空) a i ≤b j ,则将 a i 插入到 Lc 中 ,否则,将bj 插入到 Lc中;
4. 重复 2 和 3 两步,直至La或Lb中的元素被取完为止;
5. 将La表或Lb表中剩余元素复制插入到Lc表中。
void MergeList(List La, List Lb, List &Lc){
    int La_len, Lb_len, i=1, j=1, k=0;
    ElemType ai, bj;
    InitList(Lc);
    La_len = ListLength(La);
    Lb_len = ListLength(Lb);
    //La Lb均非空
    while( (i<=La_len) && (j<=Lb_len) ){
        GetElem(La, i, &ai);
        GetElem(Lb, j, &bj);
        if (ai<=bj) 
            {ListInsert(Lc, ++k, ai); ++i;}
        else        
            {ListInsert(Lc, ++k, bj); ++j;}
    }
    //La非空
    while(i<=La_len){
        GetElem(La, i++, &ai);//先用后加
        ListInsert(Lc, ++k, ai);//先加后用
    }
    //Lb非空
    while(j<=Lb_len){
        GetElem(Lb, j++, &bj);//先用后加
        ListInsert(Lc, ++k, bj);//先加后用
    }
}
3、比较
前提条件
        • 假如 GetElem 和 ListInsert 这两个操作的执行时间和表长无关
        • LocateElem 的执行时间和表长成正比
时间复杂度
        • 算法1: O( ListLength(La) × ListLength(Lb) )
        • 算法2: O( ListLength(La) + ListLength(Lb) )

二、线性表的顺序表示和实现

表示

  • 一组地址连续的存储单元依次存储线性表的数据元素
  • 设有非空的线性表 (a 1 a 2 …a n ) ,而 每个元素需占用 X 个存储单元, 则:
    • 线性表中第 i+1个数据元素的存储位置LOC(ai+1)和第 i个数据元素的存储位置LOC(ai)之间满足下列关系: LOC(a i+1 )=LOC(a i )+X
    • 线性表的第 i 个数据元素 ai的存储位置为:LOC(a i )= LOC(a 1 ) +(i-1)* X

实现

线性表的顺序实现:用动态分配的一维数组

#define LIST_INIT_SIZE 100 //线性表初始大小
#define LISTINCREMENT 10  //线性表增量大小
typedef struct{
    ElemType *elem;  //基地址
    int length;     //当前长度
    int listsize;  //当前分配的存储容量,以sizeof(ElemType)为单位
}SqList;
1、初始化:O(1)
Status InitList_Sq( SqList &L){
    //构造一个空的、容量为LIST_INIT_SIZE的线性表L
    L.elem=(ElemType*)malloc(LIST_INIT_SIZE*sizeof(ElemType));
    //指向的第一个元素

    if (!L.elem ) exit(OVERFLOW); //存储分配失败
    L.length = 0; //空表长度为0
    L.listsize = LIST_INIT_SIZE; //初始存储容量
    return OK; 
}

• malloc 是用于分配指定size的内存的库函数
• 将L.elem这个指针指向一块通过malloc函数分配的内存的地址
• 强制转换成ElemType类型的指针

2、元素插入:O(ListLength(L)) → O(n)

在第i个元素之前插入元素e

Status ListInsert_Sq( SqList &L, int i, ElemType e){
    //i值不合法
    if(i<1 || i>L.length+1) return ERROR;
    //当前存储空间已满,增加容量
    if(L.length>=L.listsize){
        ElemType *newbase=(ElemType *) realloc(L.elem(L.listsize+LISTINCREMENT)*sizeof(ElemType));
        if (!newbase) return ERROR; // 存储分配失败
        L.elem = newbase; //新的基地址
        L.listsize += LISTINCREMENT; //新的存储容量
    }
    ElemType *p;
    ElemType *q = &(L.elem[i-1]);
    for(p=&(L.elem[L.length-1]);p>=q;--p){
        *(p+1)=*p;
    }
    *q=e;
    ++L.length;
    return OK;
}
realloc函数用于修改一个原先已经分配的内存块的大小,可以使一块内存扩大或缩小。
 
3、元素删除:O(n)

删除线性表的第i个元素

Status ListDelete_Sq(Sq_List &L, int i, ElemType &e){
    ElemType *p, *q;
    if ((i<1) || (i>L.length)) return ERROR; // i值不合法
    p = &(L.elem[i-1]);// p为被删除元素的位置(下标从0开始)
    e = *p;// 被删除元素的值赋给e
    q = L.elem+L.length-1;// q为表尾元素的位置
    for (++p;p<=q;++p)
        *(p-1)=*p;
    --L.length;
    return OK;
}
比较插入和删除
  • 顺序存储结构的线性表中插入或删除元素
    • 时间主要耗费在移动元素上 O(n)
    • 移动元素的个数取决于插入或删除元素的位置
4、查找元素:O(L.length)
#define LESS -1
#define GREATER 1
Status (*compare)(ElemType a, ElemType b)
{
    if (a<b) return LESS;
    if (a>b) return GREATER;
    return 0; //相等
}

int LocateElem_Sq(SqList L, ElemType e, Status(*compare)(ElemType, ElemType))
{
    int i;
    ElemType *p;
    i=1; // i的初值为第1个元素的位序 (其实只是一个计数)
    p=L.elem; // p的初值为第1个元素的存储位置
    while (i <= L.length && !(*compare)(*p++, e)) ++i; 
    // p先取后加,从第一个开始比较
    // 相等时跳出循环,即0
    if (i <= L.length) return i; 
    else return 0; 
}
5、合并“元素赋值”:O(La.length+Lb.length)
void MergeList_Sq(SqList La, SqList Lb, SqList &Lc){
    ElemType *pa, *pb, *pc, *pa_last, *pb_last;
    pa = La.elem; pb = Lb,elem;
    Lc.listsize = Lc.length = La.length+Lb.length;
    pc = Lc.elem = (ElemType*)malloc(Lc.listsize*sizeof(ElemType));
    if (!Lc.elem) exit(OVERFLOW);// 存储分配失败
    pa_last=La.elem+La.length-1;
    pb_last=Lb.elem+Lb.length-1;
    while(pa<=pa_last && pb<=pb_last){
        if(*pa<=*pb) *pc++=*pa++; // 按值非递减插入
        else *pc++=*pb++;
    while(pa<=pa_last) *pc++=*pa++;
    while(pb<=pb_last) *pc++=*pb++;
}

三、线性表的链式表示和实现

表示

  • 指用一组 任意的 (连续的或不连续的)存储单元存储线性表中的数据元素
  • 存储方式
    • 在存储每个元素值的同时,存储指示其直接后继的地,称为指针(pointer)或链(link),这两部分组成链表中的结点
    • 指针建立了数据元素之间的逻辑关系
  •  每个结点由两个域组成:
    • 数据域data:存放结点的值
    • 指针域next:存放结点的直接后继的地址

链式存储

  • 链表是通过每个结点的指针域将线性表的n个结点按其逻辑次序链接在一起的
  • 线性链表/单链表:每一个结点每一个结点只包含一个指向直接后继的指针域
    • 基于C指针实现的单链
    • 基于C数组实现的单链表/静态链表
  • 双向链表:每一个结点包含两个指针域,其一指向直接后继,另一指向直接前驱
  • 循环链表:整个链表的指针域链接成一个环
  • 双向循环链表:将头结点和尾结点链接起来的双向链表

四、单链表

  • 为操作方便,总是在链表的第一个结点(首元结点)之前附设一个头结点
    • 头结点的数据域可以不存储任何信息(或存储链表长度等信息)
    • 头结点的指针域存储指向第一个结点的指针(即第一个结点的存储位置)

(1)C指针实现

  • 结点的类型定义

typedef struct LNode{
    ElemType data;       /*数据域,保存结点的值 */
    struct LNode *next;  /*指针域*/
}LNode, *LinkList;       /*结点的类型 */
  • 结点的赋值

LNode *p;
p=(LNode*)malloc(sizeof(LNode));
p->data=20;p->next=NULL;

  • 基本操作的实现

//取第i个元素
GetElem(L, i, &e)
//在第i个元素之前插入元素e
ListInsert(&L, i, e)
//删除第i个元素
ListDelete_L(&L, i, &e)
//生成n个元素的链表
CreateList(&L,n) 
//两有序表合并成一新有序表
MergeList(La, Lb, &Lc)
链表不是随机存取 (random access)结构
数组是一块物理上相连的存储空间,只要知道该数组的首元素的地址,之后的元素在此基础上加位置号,就可以找到想要的元素,这就是随机存取。
即:不需要先找a,再找b,最后找到c; 而是直接利用“数组开始位置+2 ”就是c元素。

1、单链表的查找:O(n)

Status GetElem_L(LinkList L, int i, ElemType &e){
    // L为带头结点的单链表的头指针
    // 当第i个元素存在时,其值赋给e并返回OK,否则返回ERROR 
    // 初始化,p指向第一个结点,j为计数器
    LinkList p; p=L->next; int j=1;
    while(p && j<i){
        //顺指针向后查找,直到p指向第i个元素或p为空
        p=p->next;
        ++j;
    }
    if(!p || j<i) return ERROR;
    e=p->data;
    return OK;
}

2、单链表的插入:O(n)

算法思想
  • 将值为e的新结点插入到表的第i个结点位置上
  • ①首先找到第i-1个结点p
  • ②然后生成一个数据域为e的新结点s
  • ③并将s结点作为p的直接后继结点
  • 解决了顺序表的插入操作需要移动大量元素的问题

Status ListInsert_L(LinkList &L, int i, ElemType e){
    //在带头结点的单链表L的第i个元素之前插入元素e
    LinkList p,s;
    p=L; //头结点
    int j=0;
    while(p && j<i-1){ // 寻找第i-1个结点
        p=p->next;
        ++j;
    }
    // 第一次,p为(第一个)首结点,j=1
    // 跳出循环时,j=i-1,p为第i-1个结点
    
    // i小于1(导致j>i-1成立)或者大于表长(导致p为NULL)
    if (!p || j > i-1) return ERROR;
    s=(LinkList*)malloc(sizeof(LNode));//生成新结点
    s->data=e;
    s-next=p->next;
    p->next=s;
    return OK;
}

3、单链表结点的删除:O(n)

算法思想:
  • 为了删除第i个结点ai ,必须找到结点的存储地址
  • 该存储地址是在其直接前趋结点ai-1的next域中
  • ①首先找到ai-1的存储位置p
  • ②然后令p–>next指向ai 的直接后继结点,即把ai 从链上摘下
  • ③最后释放结点ai 的空间

解决了顺序表的删除操作需要移动大量元素的问题

删去第i个结点仅当1≦i≦n时是合法的

        • 当i=n+1时,虽然被删结点不存在但其前趋结点却存在,是终结点,故判断条件之一是p–>next!=NULL,否则会删掉终结点

 

Status ListDelete_L(LinkList &L, int i, ElemType &e){
    //在带头结点的单链表L中删除第i个元素,并由e返回其值
    LinkList p,q;
    p=L;
    int j=0;
    while (p->next && j<i-1){
        // 寻找第i个结点,并令p指向其前趋
        p = p->next; 
        ++j; 
    }
    // 初始状态,p为头结点,p->next非空,j=0
    // 跳出循环时,j=i-1,p为第i-1个结点
    if (!(p->next) || j>i-1) return ERROR;
    //即要删除的结点i非空,且i的值合法
    q=p->next;
    p->next=q->next;
    // 删除并释放结点
    e=q->data;
    free(q);
    return OK;
}  

4、创建单链表:O(ListLength(L))

  • 逆序输入 n 个数据元素的值,建立带有头结点的单链表
  • 具体操作:
    • 建立一个空表
    • 输入数据元素an,建立结点并插入
    • 输入数据元素an-1,建立结点并插入
    • 依次类推,直至输入a1为止

void CreateList_L(LinkList &L, int n){
    //逆序输入随机生成的n个元素的值
    //建立带表头结点的单链表L
    LinkList p; int i;
    //先建立一个带头结点的空单链表
    L=(LinkList)malloc(sizeof(LNode));
    L->next=NULL;
    for(i=n;i>0;--i){
        p=(LinkList)malloc(sizeof(LNode));
        p->data=random(200);
        p->next=L->next;
        L->next=p;
    }
}

5、单链表的合并:O(m+n)

void MergeList_L(LinkList &La, LinkList &Lb, LinkList &Lc){
    //合并 两非递减单链表La和Lb,成新的非递减单链表Lc
    LinkList pa,pb,pc;
    //pa, pb为两个链表的当前结点,pc为合并后链表的最后一个结点
    pa=La->next;
    pb=Lb->next;
    Lc=pc=La;// 用La的头结点作为Lc的头结点
    while(pa && pb){
        if(pa->data<=pb->data){
            pc->next=pa;
            pc=pa;//更新pc
            pa=pa->next;//更新pa
        }
        else{
            pc->next=pb;
            pc=pb;//更新pc
            pa=pa->next;//更新pa
        }
    }
    pc-next=pa?pa:pb;
    free(Lb);// 释放Lb的头结点
}   

6、应用:一元多项式

(2)C数组实现:静态链表

  • 数组的一个分量表示一个结点
  • 游标(指示器cur)代替指针指示结点在数组中的相对位置
  • 数组的第零分量可看成是头结点,其指针域指示链表的第一个节点
  • 需要预先分配一个较大空间,但在插入和删除时不需要移动元素,仅需修改指针
  • 保持了链式存储结构的主要优点

定义

#define MAXSIZE 1000
typedef struct SLinked{
    ElemType data;
    int cur;
}SLinkedList[MAXSIZE];
SLinkedList s;
  • 在该存储数组中,可以包含一个空闲链表和多个静态链表

  • 用(s,head)表示在s中存储的以head为起始位置的静态链表

基本操作的实现

//1. 静态链表的初始化:在存储数组中建立空闲链表
void InitList(SLinkedList space);
//2. 创建一个含有n个结点的静态链表,返回表头在存储数组的位置
int CreateList(SLinkedList space,int n);
//3. 在以head为表头的静态链表中,在第i个结点之前插入一个值为x
的新结点
int Insert(SLinkedList space, int head, int i, ElemType x);
//4. 在以head为表头的静态链表中,删除第i个结点
int Delete(SLinkedList space,int head,int i, ElemType *e);
//5. 在以head为表头的静态链表中,确定第一个值为x的结点的位置
int Locate(SLinkedList space, int head,ElemType x);

1、初始化

//将一维数组space中各分量链成一个空闲链表
//space[0].cur为空闲链表的头指针,0表示空指针
void InitList(SLinkedList space){
    for(int i=0; i<MAXSIZE-1; i++)
        space[i].cur = i+1;
    space[MAXSIZE-1].cur = 0;
}

2、创建一个静态链表

//创建一个含有n个结点的静态链表,返回表头在存储数组的位置
int CreateList(SLinkedList space,int n){ 
    int head, k, s, i;
    k = MallocNode(space); //从空闲链表中取得一个空结点
    head = k;
    for (i = 1; i <= n; i++){
        s = AllocNode (space);
        scanf ("%d", &space[s].data);
        space[k].cur = s;
        k = s;
    }
    space[k].cur = 0;
    return head;
}

3、分配和释放

// 若空闲链表非空,则返回分配的结点下标,否则返回0 
int MallocNode(SLinkedList space){
    int i; 
    i = space[0].cur;
    if (i == 0) return 0;
    space[0].cur = space[i].cur; //space中少了 i 这个结点
    return i; 
}

// 将下标为 i 的空闲结点回收到备用链表
void FreeNode(SLinkedList space, int i){
    space[i].cur = space[0].cur; //先让space[i]指向space[0]的下一个
    space[0].cur = i; //再让space[0]指向space[i]
} //相当于插入space[i]

4、插入结点

//在以head为表头的静态链表中,在第i个结点之前插入一个值为x的新结点
int Insert(SLinkedList space,int head,int i,ElemType x){
    int j,k,m; 
    if(i<1) return 0;
    k=head; 
    j=0;
    while (k!=0 && j<i-1){ //查找第i-1个结点
        j++; 
        k=space[k].cur; 
    }
    if(k==0) return 0;
    m=MallocNode(space); //从空闲链表中获取结点,m为该结点下标
    if (m!=0){
        space[m].data=x;
        space[m].cur=space[k].cur; //原来k后面的链接到m上
        space[k].cur=m; //m放在第i-1个结点后,即第i个结点上
        return 1;
    } 
    else return 0; 
}

5、删除结点

//在以head为表头的静态链表中,删除第i个结点
int Delete(SLinkedList space,int head,int i, ElemType *e){
    int j,k,m; 
    if(i<1) return 0;
    k=head; 
    j=0;
    while (k!=0 && j <i-1){ //查找第i-1个结点
        j++; 
        k=space[k].cur;
    }
    if(k==0) return 0;
    m=space[k].cur; //第i个结点
    space[k].cur=space[m].cur; //第i个后面的结点
    *e=space[m].data; //将取出的结点值存在e中
    FreeNode(space,m); //释放
    return 1;
}

6、查找结点

// 在以head为表头的静态链表中,确定第1个值为x的结点的位置
// 若找到,则返回它在存储数组中的位置,否则返回0
int Locate(SLinkedList space, int head,ElemType x){
    int k;
    k=space[head].cur; // k指示静态链表中的第一个结点
    while(k!=0 && space[k].data!=x)
        k=space[k].cur; //顺链查找,如果没找到也会返回0
    return k;
}

7、应用:集合合并(只保留不相同的元素)

  • 算法思想
    • 建立表示集合A的静态链表S
    • 在输入集合B的元素时,查找S表
    • 若存在和B相同的元素,则从S表中删除掉否则将此元素插入S表
void MergeAB(SLinkedList A, int ha, SLinkedList B, int hb){
    int i,j,k,m; 
    ElemType x;
    j=B[hb].cur;
    while(j!=0){ // 对hb中的每个结点,进行下面的步骤
        x=B[j].data;
        i=Locate(A,ha,x); //链表ha中查找有无hb中的元素
        if(i==0) Insert(A,ha,1,x); //将该元素插入ha,成为ha的第一个元素
        else { // ha有hb中的元素,则将该元素从ha中删除
            m=0;
            k=ha;
            while(k!=i){m++;k=A[k].cur;}
            Delete(A,ha,m,&x);
        }
    j=B[j].cur;
    } 
}

五、双向链表Double Linked List

 定义:

  • 双向链表:构成链表的每个结点中设立两个指针域
    • 一个指向其直接前趋的指针域prior
    • 一个指向其直接后继的指针域next

typedef struct node{
    ElemType data;
    struct node *prior, *next;
}DoubleLinkedList;
  • 特性:DoubleLinkedList p;
  • p->piror->next=p=p->next->prior

基本操作的实现:

//1. 创建长度为n的双向链表
DoubleLinkedList *CreateDoubleLinkedList(int n);
//2. 在双向链表中查找第1个值为e的结点
DoubleLinkedList *Locate(DoubleLinkedList *head, ElemType e);
//3. 在双向链表中查找第i个结点,返回指向该结点的指针
DoubleLinkedList *GetElemP(DoubleLinkedList *head,int i);
//4.在双向链表第i个结点之前插入元素e
int InsertElem(DoubleLinkedList *head,int i,ElemType e);
//5. 在双向链表中删除第i个结点,并返回结点的元素值
int DeleteNode(DoubleLinkedList *head,int i,ElemType *e);

1、创建

//长度为n
DoubleLinkedList *CreateDoubleLinkedList(int n){
    DoubleLinkedList *head, *p, *s, int i;
    //创建头结点
    p=head=(DoubleLinkedList *)malloc(sizeof(DoubleLinkedList));
    for(i=1;i<=n;i++){
        s=(DoubleLinkedList *)malloc(sizeof(DoubleLinkedList));
        scanf("%d",&s->data);
        s->prior=p;
        p->next=s;
        p=s;
    }
    p-next=head;
    head->prior=p;
    return head;
}

2、查找e

// 查找第1个值为e的结点
DoubleLinkedList *Locate(DoubleLinkedList *head, ElemType e){
    DoubleLinkedList *p;
    p=head->next; // p指向第一个结点
    while(p!=head && p->data!=e)
        p=p->next;
    if(p==head) return NULL;
    else return p;
}

3、查找i

// 在双向链表中查找第i个结点
DoubleLinkedList *GetElemP(DoubleLinkedList *head, int i){
    DoubleLinkedList *p;
    int j;
    if(i<1) return 0;
    p=head->next;
    j=1;
    while(p!=head && j<i){
        // 顺指针向后查找,直到p指向第i个结点或p为空
        p=p->next;
        j++;
    }
    if(p==head && j<i) return NULL;// 第i个元素不存在
    return p;
}

4、插入

需要同时修改两个方向上的指针

  • 若仅已知直接前驱结点p,拉链时必须注意先后次序
  • S=(DoubleLinkedList *)malloc(sizeof(DoubleLinkedList));
    S->data=e;
    (1) S->next=p->next;
    (2) p->next->prior=S;
    (3) p->next=S;
    (4) S->prior=p;

  •  若同时给出直接前驱结点p和直接后继结点q,那么无须注意先后次序
  • S=(DoubleLinkedList *)malloc(sizeof(DoubleLinkedList));
    S->data=e;
    p->next=S; S->next=q;
    S->prior=p; q->prior=S;

int InsertElem(DoubleLinkedList *head, int i, ElemType e){
    DoubleLinkedList *p,*q;
    int j;
    if(i<1) return 0;// i的合法值为1≤i≤表长+1
    //找前驱
    while(p->next!=head && j<i){
        p=p->next;
        j++;
    } 
    if(p->next!=head || (p->next == head && j==i-1)){
        q=(DoubleLinkedList *)malloc(sizeof(DoubleLinkedList));
        q->data=e;
        q->next=p->next;
        q->prior=p;
        p->next->prior=q;
        p->next=q;
        return 1;
    }
    else return 0; //第i个元素不存在
}  

5、删除

需要同时修改两个方向上的指针

  • 若删除的结点为p:
    • p->prior->next = p->next;
      p->next->prior = p->prior;
      free(p);
  • 若删除结点的前驱结点为p:
    • p->next=p->next->next;
    • p->next->prior=p;
int DeleteNode(DoubleLinkedList *head, int i, ElemType *e){
    DoubleLinkedList *p;
    if(i<1) return 0;
    //在head中确定第i个结点的位置指针p 
    p=GetElemP(head,i);
    if(!p) return 0;//不存在
    *e=p->data;
    p->prior->next=p->next;
    p->next->prior=p->prior;
    free(p);
    return 1;
}

六、循环链表Circular Linked List

单链循环链表

  • 最后一个结点的指针域指向链表的头结点
  • 对于单循环链表,除链表的合并外,其它的操作和单线性链表 基本上一致,仅仅需要在单线性链表操作算法基础上作以下 单修改
    • 判断是否是空链表:head->next==head
    • 判断是否是表尾结点:p->next==head

  • ???
  • 设置头指针、尾指针的循环链表
    • 例如:两个循环链表的合并
  • 仅设置尾指针的循环链表
    • A尾→B头;B尾→A头

双向循环链表

  • 具有指向前驱和后继的指针,这样,从循环链表的任意一个结点出发都可以找到链表中的其它结点,使得表处理更加方便灵活

七、链式存储方式的比较


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值