c語言實現雙鏈表


  中午寫了一篇關於單鏈表的博客。好吧,我並沒有搜到我寫的這篇文章。但我還是要寫下去,萬一有人看到了呢……不過,呵呵。。。

雙鏈表和單鏈表的操作大同小異,只是多了一個前驅指針,我是這樣定義的。

typedef struct node
{
    int data;
    struct node * previous;
    struct node * next;
} Node;

typedef struct dlist
{
    Node * head;
} Dlist;

 與單鏈表相比,它只多了一個前驅節點。請看圖示!

這里用prev代表previous,我在第一次寫雙鏈表時,previous與next到底指向哪里糾結了半天,從圖上來看,previous好像指向了前驅的next,然而並不是。

previous和next指向的都是節點的首地址,並不是previous或者next。也就是說previous和next里面存的是前驅節點和后繼節點的地址值。

為了操作方便,我們設計了一個頭節點。

void d_init(Dlist * list)
{
    list->head = make_node(INT_MIN);//創建頭節點,並賦值一個整形的最小值
}

因為雙鏈表具有前驅指針,所以我們可以方便的找到要操作的節點的前驅和后繼。

插入操作,要實現順序鏈表,所以第一步還是找到插入位置,第二步進行插入。請看圖示!

先做1、2步,再做3、4步,這里主要是想先講新節點鏈接到鏈表上,1、2誰先並沒有關系。然后將3節點的后繼與5節點的前驅鏈接到新節點上。

這里還有一種情況,就是在最后的位置插入新節點。這樣只需將將新節點與前驅節點鏈接就可以了。就是不做1、4步,先做2、再做3就可以了。

if ( current->next == NULL )//在鏈表的尾端插入和中間插入不同
    {
        current->next = node;
        node->previous = current;
    }
    else
    {
        node->previous = current->previous; //將新節點的前驅指向當前節點的前驅
        node->next = current;//將新節點的后繼指向當前節點,所以這時一個前插方式

        current->previous->next = node;//將當前節點的前驅的后繼指向新節點
        current->previous = node;//將當前節點的前驅指向新節點
    }

 

刪除操作,還是先找到刪除節點。然后將刪除節點從鏈表中摘出來,釋放內存。請看圖示!

由於我們只知道current指針,所以我們找到3節點的后繼指針的辦法是current->previous->next。而找5節點前驅指針也是類似的,current->next->privous。然后就可以釋放刪除節點了。

主要的操作已經講完了,接下來看源碼吧。

還是有兩部分組成,dlist.h和dlist.c

dlist.h

#ifndef _DLIST_H
#define _DLIST_H

typedef struct node
{
    int data;
    struct node * previous;
    struct node * next;
} Node;

typedef struct dlist
{
    Node * head;
} Dlist;

void d_init(Dlist * list);//初始化
bool d_insert(Dlist * list, const int data);//插入
bool d_remove(Dlist * list, const int key);//移除
bool d_modify(Dlist * list, const int key, const int data);//修改
Node* d_find(Dlist * list, const int key);//查找
void d_treaverse(Dlist * list, void (*func)(void * pointer) );//遍歷
void d_destroy(Dlist * list);//銷毀

#endif //_DLIST_H

dlist.c

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <limits.h>
#include <stdbool.h>

#include "dlist.h"

static Node* make_node(const int data);//創建節點
static void destroy_node(void * pointer);//銷毀節點
static void treaverse_list(Node * current, void (*func)(void * pointer) );//遍歷鏈表,包括頭節點

void d_init(Dlist * list)
{
    list->head = make_node(INT_MIN);//創建頭節點,並賦值一個整形的最小值
}

bool d_insert(Dlist * list, const int data)
{
    Node * current = list->head;//當前指向頭節點
    Node * node;//新節點指針
    
    while ( current->next != NULL && current->data < data) //找到插入位置
        current = current->next;//在不是尾節點結束時,當前指針的值要大於新值,所以插入在當前節點之前

    if ( current->data == data )//如果數據重復,插入失敗
        return false;
    
    node = make_node(data);

    if ( current->next == NULL )//在鏈表的尾端插入和中間插入不同
    {
        current->next = node;
        node->previous = current;
    }
    else
    {
        node->previous = current->previous; //將新節點的前驅指向當前節點的前驅
        node->next = current;//將新節點的后繼指向當前節點,所以這時一個前插方式

        current->previous->next = node;//將當前節點的前驅的后繼指向新節點
        current->previous = node;//將當前節點的前驅指向新節點
    }

    return true;
}

bool d_remove(Dlist * list, const int key)
{
    Node * current = list->head;//當前指針指向頭節點

    while ( (current = current->next) != NULL && current->data < key) ;//找到關鍵元素,由於是順序鏈表,所以不用全部遍歷全部鏈表,只要找到第一不小於關鍵key的位置

    if ( current->next == NULL && current->data == key )//刪除最后一個節點和中間節點方法不同
        current->previous->next = NULL;//刪除最后一個節點
    else if ( current->data == key )//刪除中間節點
    {
        current->previous->next = current->next;//當前節點的前驅的后繼指向當前節點的后繼
        current->next->previous = current->previous;//當前節點的后繼的前驅指向當前節點的前驅
    }
    else
        return false;//沒有找到關鍵字,返回false

    free(current);//釋放當前指針內存

    return true;
}

bool d_modify(Dlist * list, const int key, const int data)//修改元素,因為是有序鏈表,所以要先刪除后插入
{
    if ( d_remove(list, key) )
        if ( d_insert(list, data) )
            return true;
    
    return false;
}

Node* d_find(Dlist * list, const int key)
{
    Node * current = list->head;//當前指針指向頭節點

    while ( (current = current->next) != NULL && current->data < key );//找到關鍵字位置

    if (current->data == key)//判斷查找的元素是否存在
        return current;
    else
        return NULL;
}

void d_treaverse(Dlist * list, void (*func)(void * pointer) )
{
    treaverse_list(list->head->next, func);//遍歷,不包括頭節點
}

void d_destroy(Dlist * list)
{
    treaverse_list(list->head, destroy_node); //遍歷,銷毀鏈表
    list->head = NULL;
}

static void treaverse_list(Node * current, void (*func)(void * pointer) )//這里使用函數指針,是為了程序的可擴展行,本程序中,d_treaverse 和 d_destroy 函數都用該函數實現不同功能
{
    while ( current != NULL )//遍歷,包括頭節點
    {
        func(current);
        current = ( (Node *)current)->next; //強制類型轉換,由於->的優先級高於(),所以用括號改變結合順序
    }
}

static void destroy_node(void * pointer)//釋放內存
{
    free((Node *)pointer);
}

static Node* make_node(const int data)//創建節點
{
    Node * node = (Node *)malloc( sizeof(Node) );
    if ( node == NULL )
    {
        exit(1);
        printf("動態分配內存失敗,程序結束.");
    }    
    node->data = data;
    node->previous = NULL;
    node->next = NULL;

    return node;
}

還有一件事忘說了,這些代碼都是在gcc下調試過的,我電腦沒裝Windows,所以就沒有實驗,不過問題應該不大,如果哪里出現問題,請聯系我,歡迎大家指正。。。

2016-01-10


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM