数据结构:线性表之链表的总结

1 单链表

1.1 初始化

1.1.1 不带头结点的单链表

typedef struct LNode{    //定义单链表结点类型
    ElemType data;       
    struct LNode *next;  //指针指向下一个结点
}LNode, *LinkList;

//初始化一个空的单链表(不带头结点)
bool InitList(LinkList &L) {
    L = NULL;            //空表,暂时没有任何结点
    return true;
}

//判断单链表是否为空
bool Empty (LinkList L) {
    if (L == NULL)
        return true;
    else
        return false;
}

void main(){
    LinkList L;          //声明一个指向单链表的指针
    //初始化一个空表
    InitList(L):
    //……
}
    

注:

  1. 此操作只是声明一个指向单链表的指针,然后使该指针指向空,这个单链表里面还没有结点
  2. 检查此时单链表是否为空,只需要看L这个指针是否为空即可
  3. L即为头指针
  4. 当单链表存放数据时,头指针指向第一个实际存放数据的结点

1.1.2 带头结点的单链表

typedef struct LNode{    //定义单链表结点类型
    ElemType data;       
    struct LNode *next;  //指针指向下一个结点
}LNode, *LinkList;

//初始化一个单链表(带头结点)
bool InitList(LinkList &L) {
    L = (LNode*)malloc(sizeof(LNode)); //分配一个头结点,头指针L指向头结点
    if (L == NULL)
        return false;       //头指针后面应该有头结点,如果没有,说明空间不足,头结点分配失败
    L->next = NULL;         //头结点之后是空的,说明还没有其他结点
    return true;                
}

//判断单链表是否为空
bool Empty (LinkList L) {
    if (L->next == NULL)    头结点之后是空的,说明还没有其他结点
        return true;
    else
        return false;
}

void main(){
    LinkList L;          //声明一个指向单链表的指针
    //初始化一个空表
    InitList(L):
    //……
}
    

注:

  1. 头指针L指向头结点
  2. 头结点中还没有数据
  3. 判断此时单链表是否为空,只需判断头结点之后是否为空
  4. 当单链表存放数据时,头指针指向头结点,第一个带数据的结点在头结点之后

1.1.3 LNode 、LNode * 、LinkList的联系与区别

1、LNode 是struct LNode这个结构体的别名,而这个结构体是用来创建单链表的结点。
2、LinkList 是指向单链表结点的指针
3、LNode* 等价于LinkList
4、当强调这是结点时使用LNode* ;强调这是单链表时使用LinkList

1.2 按位序插入

1.2.1 带头结点的单链表——O(n)

思想:

  1. 判断插入的位序 i 是否合法,若此单链表结点的个数为n,则可插入的位序的范围为(1,n+1)
  2. 循环找到第 i -1 个结点
  3. 为新插入的结点分配空间,并将数据赋给新结点
  4. 将新结点插入
typedef struct LNode {
    ElemType data;
    struct LNode *next;
}LNode, *LinkList;

//按位序插入——带头结点
//在第i个位置插入元素e
bool ListInsert (LinkList &L, int i, ElemType e){
    if( i<1 ){                  //【1】判断插入的位序是否>=1
        return false;
    }
    LNode *p;
    int j=0;
    p = L;                      //L为头结点,头结点是第0个结点,第一个结点是存放数据的结点
    while ( p!=NULL && j<i-1){  //【2】循环找到第i-1个结点   
        p = p->next;
        j++;
    }
    if (p == NULL){             //【1】判断插入的位序是否<=n
        return false;
    }
    LNode *s = (LNode*)malloc(sizeof(LNode)); //【3】为新结点分配空间,并定义指针s指向新结点
    s->data = e;                //【3】将数据赋值给新结点
    s->next = p->next;          //【4】插入新结点
    p->next = s;
    return true;
}

注:

  1. 判断插入的位序是否符合(1,n+1)时,由于单链表的结点个数n是未知的,所以两个条件分别进行判断
  2. 插入的两行代码的顺序不可变换

1.2.2 不带头结点的单链表——O(n)

思想:

  1. 判断插入的位序 i 是否 >= 1
  2. 由于不带头结点的单链表的头指针是指向第一个结点的,因此,若插入的结点为第一个结点时,插入操作结束后,还需将头指针重新指向新插入的结点。因此,对于 i = 1 时,单独操作。
  3. i > 1的操作与带头结点的步骤相同
typedef struct LNode {
    ElemType data;
    struct LNode *next;
}LNode, *LinkList;

//2、按位序插入——不带头结点
//在第i个位置插入元素e
bool ListInsert (LinkList &L, int i, ElemType e){
    if( i<1 ){                  //【1】判断插入的位序是否>=1
        return false;
    }
    if ( i == 1 ){
        LNode *s = (LNode *)malloc(sizeof(LNode));
        s->data = e;
        s->next = L;
        L = s;                  //头指针指向新结点
        return true;
    }
    LNode *p;
    int j=1;                    //注意:j的值和带头结点的单链表的值的操作中不同
    p = L;                      //p指向第一个结点
    
    while ( p!=NULL && j<i-1){  //【2】循环找到第i-1个结点   
        p = p->next;
        j++;
    }
    if (p == NULL){             //【1】判断插入的位序是否<=n
        return false;
    }
    LNode *s = (LNode*)malloc(sizeof(LNode)); //【3】为新结点分配空间,并定义指针s指向新结点
    s->data = e;                //【3】将数据赋值给新结点
    s->next = p->next;          //【4】插入新结点
    p->next = s;
    return true;
}

注:

  1. 注意 j 的值在带头结点的单链表和不带头结点的单链表中的区别。
  2. 带头结点的单链表中,有第0个结点,即头结点;不带头结点的单链表中直接从第一个结点开始。 j 代表的就是指针 p 指向的是第几个结点。

1.2.3 指定结点的后插——O(1)

思想:

  1. 判断指定的结点p是否存在
  2. 为新元素e分配空间,并检测是否分配成功
  3. 插入
//指定结点的后插操作
//在p结点之后插入元素e
bool insertNextNode (LNode *p, ElemType e){
    if (p == NULL){             //【1】判断p结点是否存在
        return false;
    }
    LNode *s = (LNode*)malloc(sizeof(LNode)); //【2】为新结点分配空间,并定义指针s指向新结点
    if (s == NULL){
        return false;
    }
    s->data = e;                //【3】将数据赋值给新结点
    s->next = p->next;          //【4】插入新结点
    p->next = s;
    return true;
}

注:

  1. 其实按位序插入就是对第i-1个元素进行后插操作,可在循环找到第i-1个元素后,直接调用后插操作函数进行。

1.2.4 指定结点的前插——O(1)

思想:

  1. 将新结点s插在指定结点p的后面
  2. 将s和p的数据对换
//指定结点的前插操作
//在p结点之前插入元素e
bool InsertPriorNode (LNode *p, ElemType e){
    if (p == NULL){             //【1】判断p结点是否存在
        return false;
    }
    LNode *s = (LNode*)malloc(sizeof(LNode)); 
    if (s == NULL){
        return false;
    }
    s->next = p->next;          //【3】将新结点s插在p结点之后
    p->next = s;
    s->data = p->data;          //【4】将p结点、s结点的数据对换
    p->data = e;
    return true;
}

注:

  1. 前插有两种思想,一是找到指定结点的前驱结点,再对前驱结点进行后插;二是将新结点插在指定结点之后,再将两个结点的数据对换
  2. 由于单链表想找到指定结点的前驱结点,只能在已知头指针的单链表中循环遍历找到,且时间负责度为O(n)。若头指针不可知,则无法找到。所以使用思想二更好。

1.3 删除

1.3.1 带头结点的按位序删除——O(n)

思想:

  1. 找到第i-1个元素
  2. 判断待删除的第i个元素以及第i-1个元素是否存在
  3. 将第i+1个元素连接到第i-1个元素后
  4. 返回第i个元素的值,并释放其存储空间
//删除第i个元素
bool ListDelete (LinkList &L, int i, ElemType e){
    if( i<1 ){                  
        return false;
    }
    LNode *p;                      //找到第i-1个元素
    int j=0;                    
    p = L;                      
    while ( p!=NULL && j<i-1){  
        p = p->next;
        j++;
    }
    if (p == NULL){                //判断第i-1个元素        
        return false;
    }
    if (p->next == NULL){          //判断第i个元素            
        return false;
    }
    LNode *q = p->next;            //用指针q指向第i个元素
    e = q->data;                   //返回被删除元素的数据
    p->next = q->next;             //第i+1个元素连接到第i-1个元素之后
    free(q);                       //释放第i个元素
    return true;
}

1.3.2 删除指定结点——O(n)

思想:

  1. 将结点p+1和结点p的数据交换
  2. 再将p+2连接到p结点之后
//删除指定结点p
bool DeleteNode (LNode *p){
    if ( p == NULL )
        return false;
    LNode *q = p->next;       // q 指向 p 的后继结点
    p->data = p->next->data;  // p 和 p+1 的数据交换
    p->next = q->next;        // p+2 连接到 p 后面
    free(q);
    return true;
}

注:

  • 若删除的结点p是最后一个结点,只能从头开始检索第p-1个结点——O(n)

1.4 查找

1.4.1 按位查找——O(n)

//按位查找
LNode * GetElem (LinkList L, int i){
    if(i<0)
        return NULL;            
    LNode *p;
    int j=0;                    //j表示p指向的是第几个结点
    p = L;                      //p指向头结点,即第0个结点
    while ( p!=NULL && j<i-1){  
        p = p->next;
        j++;
    }
    return p;                   //若查找的i>n,返回的p也是NULL
}

1.4.2 按值查找——O(n)

//按值查找
//找到数据为e的结点
LNode * LocaleElem( LinkList L, ElemType e ){
    LNode *p = L->next;     //p为头结点之后的第一个结点
    while ( p != NULL && p->data != e)
        p = p->next;
    return p;
}

1.5 求表长——O(n)

//求表长
int Length(LinkList L){
    int len = 0;
    LNode *p = L;
    while (p->next != NULL){
        p = p->next;
        len++;
    } 
    return len;
}

1.6 单链表的建立

1.6.1 尾插法

思想:

  1. 初始化一个空表,这里通过建立头结点来做到
  2. 每次输入的数据都存在结点s中
  3. 每个结点建立完毕后,插到尾指针之后
  4. 尾指针永远指向最后一个新建立的结点s
//尾插法建立单链表
LinkList Lis_TailInsert(LinkList &L){
    int x;                //设ElemType为整形
    L = (LinkList)malloc(sizeof(LNode));     //建立头指针,即初始化空表
    L->next = NULL;
    LNode *s, *r=L;       //r为尾指针
    scanf("%d", &x);
    while( x!= 9999 ){    //输入9999表示结束,也可使用其他数字来代替999
        s = (LNode*)malloc(sizeof(LNode));   //建立新结点
        s->data = x;
        r->next = s;      //在结点r之后插入新结点
        r = s;            //尾指针指向新结点,即尾指针永远指向最后一个结点
        scanf("%d", &x);
    }
    r->next = NULL;       //尾结点指针指空
    return L;
    
}

1.6.2 头插法

思想:

  1. 初始化空链表
  2. 建立新结点
  3. 对头指针做后插操作
//头插法建立单链表
LinkList List_HeadInsert(LinkList &L){
    LNode *s;
    int x;
    L = (LinkList)malloc(sizeof(LNode));  //初始化空链表,L为头指针
    L->next = NULL;
    scanf("%d", &x);
    while(x != 9999){
        s = (LNode*)malloc(sizeof(LNode)); //建立新结点
        s->data = x;  
        s->next = L->next;                 //对头指针进行后插
        L->next = s;
        scanf("%d", &x);
    }
    return L;
}

注:

  • 头插法的应用——链表的逆置

2 双链表

2.1 结点的定义

//双链表结点的定义
typedef struct DNode{
    ElemType data;
    struct DNode *prior, &next;
}DNode, *DLinklist;

2.2 双链表的初始化(带头结点)

//双链表的初始化(带头结点)
bool InitDLinkList (DLinklist &L){
    L = (DNode *)malloc(sizeof(DNode));  //分配一个头结点
    if (L == NULL)          //内存不足,分配失败
        return false;
    L->prior = NULL;        //头结点的prior永远指向NULL
    L->next = NULL;         //头结点之后暂时还没有结点
    return true;
}

2.3 判空——判断双链表是否为空

思想:

  • 头结点的后继指针指向的是空,即双链表为空
//双链表的判空
bool Empty(DLinklist L){
    if (L->next == NULL)
        return ture;
    else 
        return false;
}

2.4 双链表的插入

思想:

  1. 将结点s插入结点p和结点p+1之间
  2. s的后继指针指向p+1
  3. p+1的前驱指针指向s
  4. s的前驱指向p
  5. p的后继指向s
  6. 即先完成s和p+1的联系,再完成p和s的联系
//双链表的插入
//在p结点之后插入s结点
bool InsertNextDNode(DNode *p, DNode *s){
    if( p == NULL || s == NULL)
        return false;
    s->next = p->next;       // 2
    if( p->next != NULL)
        p->next->prior = s;  // 3
    s->prior = p;            // 4
    p->next = s;             // 5
}

注:

  1. 结点p和s需要判断是否合法,即是否不为空
  2. 当结点p为最后一个结点时,步骤3(p+1的前驱指针指向s)就需要考虑是否需要

2.5 双链表的删除

思想:

  1. 结点顺序分别为p、q、p+1,待删除的结点是q
  2. p的后继指向p+1
  3. p+1的前驱指向p
//双链表的删除
//删除结点p的后继结点
bool DeleteNextDNode(DNode *p){
    if( p == NULL )         //判断结点p是否合法
        return false;
    DNode *q = p->next;     //q为结点p的后继结点,即待删除的结点
    if( q == NULL )         //判断结点q是否合法
        return false;
    p->next = q->next;      //p的后继指向p+1
    if( q->next != NULL )   //考虑p+1是否为空
        q->next->prior = p; //若p+1不为空,再将p+1的前驱指向p
    free(q);                //释放删除结点的空间
    return true;
}

注:

  • 考虑p+1是否为空指针,再决定是否需要将p+1的前驱指向p

2.6 双链表的销毁

思想:

  • 循环删除头结点的后继结点
//双链表的销毁

void DestoryList(DLinklist &L){
    while ( L->next != NULL )
        DeleteNextDNode(L);   //循环删除头结点后的结点
    free(L);      //释放头结点
    L=NULL;       //头指针指向NULL
}

注:

  1. 头结点也需释放
  2. 头指针需指向空

3 循环链表

3.1 循环单链表

3.1.1 结点的定义

//循环单链表结点的定义
typedef struct LNode{
    ElemType data;
    struct LNode *next;
}LNode, *LinkList;

3.1.2 初始化

//循环单链表的初始化
bool InitList( LinkList &L) {
    L = (LNode *)malloc(sizeof(LNode));  //分配头结点
    if( L == NULL )      //内存不足,分配失败
        return false;
    L->next = L;         //自己指向自己
    return true;
}

3.1.3 判空

//循环单链表的判空
bool Empty( LinkList L ) {
    if ( L->next == L )
        return true;
    else
        return false;
}

3.1.4 表尾结点的判断

//判断循环单链表的表尾结点
bool isTail ( LinkList L, LNode *p ) {
    if ( p->next == L )  //结点p的后继是否指向头结点
        return true;
    else
        return false;
}

3.2 循环双链表

3.2.1 结点的定义

//循环双链表结点的定义
typedef struct DNode{
    ElemType data;
    struct DNode *prior, *next;
}DNode, *DLinklist;

3.2.2 初始化

//循环双链表的初始化
bool InitDLinkList( DLinklist &L) {
    L = (DNode *)malloc(sizeof(DNode));
    if ( L == NULL )
        return false;
    L->prior = L;     //头结点的前驱指向头结点
    L->next = L;      //头结点的后继指向头结点
    return true;
}

3.2.3 判空

//循环双链表的判空
bool Empty(DLinklist L) {
    if( L->next == L )
        return true;
    else
        return false;
}

3.2.4 表尾结点的判断

//判断循环双链表的表尾结点
bool isTail( DLinklist L, DNode *p) {
    if ( p->next == L ) //判断结点p的后继是不是指向头结点
        return true;
    else
        return false;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值