链表(1)

Reading time ~1 minute

##链表(1)##

链表是一组数据项的集合,其中每个数据项都是一个节点的一部分,每个节点都包含指向下一个节点的链接。在链表中各个对象按线性顺序排序。与数组不同的是,数组的线性顺序是由数组的下标决定的,而链表中的顺序是由各个对象中的指针决定的。链表可以用来简单而灵活的表示动态集合。链表是最基本的数据结构之一,也是我们经常会用到的数据结构。链表又分为单向链表,双向链表,循环链表。

单向链表 顾名思义,其链接方向是单向的,对链表的访问要通过顺序的读取从头部开始。 下面是一个简单的单向链表结构体定义

typedef struct struct_node{
 	int value;						//数据
    struct struct_node * next;		//指向下一个节点的指针
    }node_t;

双向链表 相对于单向链表,其多了一个指向前一个节点的指针,使其可以向前和向后访问节点,所以双向链表可以从任意一个节点开始读取链表的数据。 下面是一个简单的双向链表结构

typedef struct struct_node{
 	int value;						//数据
    struct struct_node * prev;		//指向前一个节点的指针
    struct struct_node * next;		//指向下一个节点的指针
    }node_t;

循环链表 与双向链表相比,循环链表是双向链表的头尾相连,即双向链表的最后一个节点的next指针指向头节点,头节点的prev指针指向最后一个节点。由于是一个循环链表,这个时候表头和表尾的边界条件就很重要了, 哨兵节点的引入可以简化边界条件,哨兵节点是一个哑对象,其在表头和表尾之间,即哨兵节点的next指向头节点,哨兵节点的prev指向尾节点。这样一个空的循环链表就仅包含哨兵节点,其next和prev指针都指向自己本身. 循环链表的定义同双向链表一样,同上不在赘述。


#####链表的基本操作#####

链表的基本操作包括插入操作,删除操作,搜索操作等。这里以循环链表为例来讲链表的基本操作。

#include <stdio.h>

typedef struct struct_node_s{
        int value;
        struct struct_node_s * prev;
        struct struct_node_s * next;
}node_t;

//初始化哨兵节点
void
node_init(node_t * h){
        h->prev = h;
        h->next = h;
}

//创建一个节点
node_t *
node_new(int v){
        node_t * p = calloc(sizeof(node_t),1);
        p->value = v;
        p->next = p;
        p->prev = p;
        return p;
}

//在添加一个节点到链表头
void
node_insert_head(node_t * h,node_t * p){
        node_t * q = h->next;
        h->next = p;
        p->prev = h;
        p->next = q;
        q->prev = p;
}

//添加一个节点到表尾
void
node_insert_tail(node_t * h,node_t * p){
        node_t * q = h->prev;
        h->prev = p;
        p->next = h;
        q->next = p;
        p->prev = q;
}

//删除一个节点
void
node_remove(node_t * p){
        node_t * h = p->prev;
        node_t * q = p->next;
        h->next = q;
        q->prev = h;
}

//释放链表中所有节点(哨兵节点除外)
void
node_free(node_t * h){
        node_t * p = h->next;
        while(p != h){
                node_remove(p);
                free(p);
                p = h->next;
        }
}

//从表头开始打印链表
void
node_print(node_t * h){
        printf("list:");
        node_t * p = h->next;
        while(p != h){
                printf("%d ",p->value);
                p=p->next;
        }
        printf("\n");
}

int main(int argc, char *argv[])
{
        printf("node_init:\n");
        node_t h;
        node_init(&h);
        node_print(&h);
        printf("node_insert_tail:\n");
        for (int i = 0; i < 10; i++) {
                node_t * p = node_new(i);
                node_insert_tail(&h,p);
        }
        node_print(&h);
        printf("node_insert_head value 10 :\n");
        node_t * p = node_new(10);
        node_insert_head(&h,p);
        node_print(&h);
        printf("node_remove value 10 :\n");
        node_remove(p);
        node_print(&h);
        node_free(&h);
        printf("node_free:\n");
        node_print(&h);
        printf("----------------end---------------\n");
        return 0;
}

输出结果:

node_init:
list:
node_insert_tail:
list:0 1 2 3 4 5 6 7 8 9 
node_insert_head value 10 :
list:10 0 1 2 3 4 5 6 7 8 9 
node_remove value 10 :
list:0 1 2 3 4 5 6 7 8 9 
node_free:
list:

—————-end—————

上面的例子只是链表的一个基本用法,这里着重将循环链表拿出来举例,是因为它是我们在实际编程中用的最多的一种数据结构。但是通常在进行Linux环境下进行C程序设计的时候,我们常用的链表是在Linux内核中的链表,详见链表(2)


参考文献:

  1. «算法导论»第10章,基本数据结构

程序运行环境:Ubuntu 12.04 LTS

排序(2)冒泡排序

# 排序(2)### 冒泡排序##冒泡排序实际上是一个非常简单的排序算法,非常容易实现----遍历文件,如果近邻两个元素大小顺序不对,就将两者交换,重复这样的操作直到整个文件排好序。如下用TypeScript做了一个简单的演示:[代码地址](https://github.c...… Continue reading

排序(1)

Published on May 01, 2016