【学习记录】双向链表 20200403

393 阅读4分钟

双向链表


在前面对于链表的学习中,我们可以发现对于链表的各个结点来说,我们可以通过next指针十分轻松地获取结点的后继结点,但是如果我们知道一个 k 结点要反过来找寻它的前驱结点 p 就会显得十分困难。通常我们会使用 p->next != k 来进行遍历找到 p,显然这是一个比较耗时的操作。

为了解决这样的问题,我们可以给链表的结点添加一个新的指针域,使其指向当前结点的前驱结点,如图所示:

由这样结点构建而成的链表就是双向链表。如图:

这是一个典型的使用空间换取时间的做法。

为了保证首元结点的操作一致性,本篇示例是使用了头结点的,具体代码在这儿

定义

对链表的结点进行定义,同时也定义了一些必要的状态。


#define MAXSIZE 20

#define SUCCESS 1
#define ERROR 0

#define TRUE 1
#define FALSE 0

typedef int ElementType;

typedef int Status;

typedef struct ChainNode{
    //    数据域
    ElementType data;
    //    前驱指针域
    struct ChainNode *front;
    //    后继指针域
    struct ChainNode *next;
}ChainNode, *ChainNodePtr;

//定义双向链表
typedef struct BothwayChain{
//    链表的头结点,当然你也可以不使用头结点,那样你需要最首元结点进行额外操作
    ChainNodePtr chain;
//    链表的长度
    int count;
}BothwayChain;

初始化

对于双向链表的初始化需要注意的是要对两个指针域的操作。

这部分还实现了一些其他的辅助操作。

  • 初始化

/// 链表的初始化
/// @param c 链表
Status chainInit(BothwayChain *c){
//    为了首元结点的操作一致性我们引出头结点。
    c->chain = (ChainNodePtr)malloc(sizeof(ChainNode));
    c->chain->front = c->chain->next = NULL;
    c->count = 0;
    printf("链表初始化完毕!------------------------------------\n\n");
    return SUCCESS;
}

  • 使用数组初始化

/// 使用一个数组来初始化一个链表
/// @param c 链表
/// @param a 数组
/// @param n 数组的长度
Status chainInitWithArr(BothwayChain *c, int a[], int n){

    chainInit(c);
    //    记录链表的尾
    ChainNodePtr tail;
    
    tail = c->chain;
    
    for (int i = 0; i < n; i++) {
        ChainNodePtr k = (ChainNodePtr)malloc(sizeof(ChainNode));
        k->data = a[i];
        k->next = NULL;
//        尾插法插入数据,注意对于操作处俩端的指针丢需要进行处理。
        k->front = tail;
        tail->next = k;
//        记录新的尾
        tail = k;
//        长度增加
        c->count++;
    }
    
    return SUCCESS;
}

  • 链表的清空

/// 链表清空
/// @param c 链表
Status chainClear(BothwayChain *c){
    ChainNodePtr p, temp;
//    清空链表的时候保留头结点
    p = c->chain->next;
    
    while (p) {
        temp = p;
        p = p->next;
        
//        这里的逻辑类似于删除
        temp->front->next = temp->next;
        if (temp->next) {
            temp->next->front = temp->front;
        }
        
//        这一步省略,temp已经被释放了
        temp->front = temp->next = NULL;
        free(temp);
    }
    return SUCCESS;
}

  • 链表判空

/// 判断链表是否为空
/// @param c 链表
Status chainIsEmpty(BothwayChain c){
//    此处你也可以使用count来进行判断
    return NULL == c.chain->next;
}

  • 遍历输出链表

/// 遍历并输出链表
/// @param c 链表
Status chainPrint(BothwayChain c){
    
    if (chainIsEmpty(c)) {
        printf("链表为空,无需输出!\n");
        return ERROR;
    }
    ChainNodePtr p;
    p = c.chain->next;
    
    printf("输出链表:\n");
    while (p) {
        printf("%d, ", p->data);
        p = p->next;
    }
    printf("\n\n");
    
    return SUCCESS;
}

  • 获取链表长度

/// 获取链表的长度
/// @param c 链表
int chainGetCount(BothwayChain c){
    return c.count;
}

插入


/// 在特定位置插入新的元素
/// @param c 链表
/// @param index 待插入位置
/// @param e 待插入元素
Status chainInsert(BothwayChain *c, int index, ElementType e){
    if (NULL == c) {
        printf("链表不存在,无法插入元素!\n");
        return ERROR;
    }
    
    ChainNodePtr p = c->chain;
    
//    寻找插入位置的前驱结点
    for (int i = 1; i < index && NULL != p; i++, p = p->next) ;
    
    if (NULL == p) {
        printf("没有找到正确的位置,无法插入元素!\n");
        return ERROR;
    }
    
    ChainNodePtr k = (ChainNodePtr)malloc(sizeof(ChainNode));
    k->data = e;
    
    k->next = p->next;
    
//    注意此处可能插在最后一位,此时 k->next = NULL 所以不能处理它的前驱指针域。
    if (NULL != k->next) {
        k->next->front = k;
    }
    
    k->front = p;
    p->next = k;
    c->count++;
    
    return SUCCESS;
}

删除


/// 删除特定位置的结点
/// @param c 链表
/// @param index 待删除位置
/// @param e 删除结点值带回
Status chainDelete(BothwayChain *c, int index, ElementType *e){
    if (NULL == c->chain) {
        printf("链表不存在,无法操作!");
        return ERROR;
    }
    ChainNodePtr p = c->chain;
    
    for (int i = 1; i < index && NULL != p; i++, p = p->next) ;
    
    if (NULL == p) {
        printf("没有找到正确的位置,无法操作!");
        return ERROR;
    }
    
    ChainNodePtr temp = p->next;
    p->next = p->next->next;
    
//    注意此处可能删除在最后一位,此时 p->next = NULL 所以不能处理它的前驱指针域。
    if (p->next) {
        p->next->front = p;
    }
   
//    带回值
    *e = temp->data;
    
//    这一步可以省略,因为temp反正是要释放的。
//    temp->front = temp->next == NULL;
    free(temp);
    
    return SUCCESS;
}


小结

双向链表在处理结点的时候主要需要注意:

  • 对于每个结点的两个指针域的处理;
  • 对于首元结点尾结点的处理。