Linux C语言实现-------双向链表

1 前言: 前几天写过一篇单链表逆序的文章点击打开链接, 本来准备第二天就把双向链表方便的东西补全的, 

奈何公(懒)务(癌)缠(发)身(作) , 拖到现在。

2 什么是链表 


老规矩 , 先上一副数据结构的图 , 希望大家加深理解

链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序通过链表中的指针链接次序实现。链表由一系列存储结点组成,结点可在运行时动态生成。每个结点均由两部分组成,即存储数据元素的数据域和存储相邻结点地址的指针域。当进行插入或删除操作时,链表只需修改相关结点的指针域即可,因此相比线性表顺序结构更加方便省时。

     链表可分为单链表(Singly Linked List)、双向链表(Doubly Linked List)和循环链表(Circular List)等。一个单链表结点仅包含一个指向其直接后继结点的指针,因此当需要操作某个结点的直接前驱结点时,必须从单链表表头开始查找。

     双向链表和循环链表均为单链表的变体。通常创建双向循环链表以综合利用两者的优点。

2.1 双向链表

双向链表的节点 , 除了有数据域之外 ,还有两个指针域, 分别指向该节点的前驱节点和后继节点,因此, 从双向链表中任意位置开始 , 都能很方便得访问其前驱和后继节点 ,  节点如图1

                    

                        图1  双向链表节点

如图1所示 , Data为该节点的数据域,prev指向直接前驱节点, 而next指向直接后继节点,通常, 双向链表会含有一个表头(也称作哨兵节点),  它的作用是能够迅速定位到链表头的位置, 简化我们对链表的任何操作包括但不限于增加,删除,遍历,查找等等操作, 双向有头非空链表结构如下图2

                

                    图2 双向有头非空链表

图中表头节点dhead的prev指向空,  C节点的next指向空, 除表头节点和尾节点外, 链表中任意节点都满足以下关系

                p = p->next->prev = p->prev->next 

也就是 , 当前节点的前驱的后继是当前节点 ,   当前节点的后继的前驱也是当前节点,  虽然有点拗口 , 但是请大家要记住。


3 双向循环链表实现:

3.1插入

假设指针p和q指向双向链表中的两个前后相邻结点,将某个新结点(指针为s)插到p和q之间,其过程及C语言描述如下图所示图3:


        图3 双向链表插入过程

请大家注意 , 插入顺序不具备唯一性 , 但是为了保证链接的完整 , 第四步操作一定要是p->next 或 q->prev,不然会丢失掉P的后继或者是q的前驱 , 这里要一定要注意,总结下伪代码如下:

s->prev = p;
s->next = q;
q->prev = s;
p->next = s;

3.2 删除

删除也就是插入的逆向操作 , 如上图, s节点成功插入到链表中, 我们删除也只需改变s节点的前驱和后继指针的指向 ,  再释放掉s节点申请的内存就行了, 伪代码如下:

p->next = s->next;
q->prev = s->prev;
free(s->data);
free(s);

3.3 查找

查找也就是从头节点开始, 遍历整张表, 遍历的时候逐一比对数据域的值就行了

代码如下:

llist.h

#ifndef ___LLIST_H__
#define ___LLIST_H__

#include <stdio.h>
#include <string.h> 
#include <stdlib.h>
    
//定义函数指针

typedef void (llist_op_t)(void *);
typedef int (llist_cmp_t)(void *, void *);
    
    
//获取字符串
#define GETLINES(string, buf) do{                           \
                        printf(string);                     \
                        fgets(buf, sizeof(buf), stdin);     \
                        if (buf[strlen(buf) - 1] == '\n')   \
                            buf[strlen(buf) - 1] = '\0';    \
                    }while(0)


//容错宏
#define ERRP(con, func, ret) do{                        \
                        if (con)                        \
                        {                               \
                            printf(#func" failed!\n");  \
                            ret;                        \
                        }                               \
                    }while(0)

    
//有头 循环 双向链表
    
//定义链表节点信息结构体
struct node_t{
    void *data;//外部用户传递数据
    struct node_t *next;//指向下一个节点地址
    struct node_t *prev;//指向上一个节点地址
};  

//抽象数据类型
typedef struct llist_t{
    struct node_t head;//数据
    int size;//类型 数据类型
    int num;//大小 数据个数
}LLIST;

//函数声明
LLIST *llist_create(int size);//初始化
int llist_append(void *data, LLIST *handle);//后插
int llist_prevend(void *data, LLIST *handle);//头查
int llist_index_insert(int index, void *data, LLIST *handle);//指定位置插入

//按关键字删除
int llist_del(void *key, llist_cmp_t *cmp, LLIST *handle);
//删除所有
int llist_all_del(void *key, llist_cmp_t *cmp, LLIST *handle);
//按位置删除
int llist_index_del(int index, LLIST *handle);

//查找
void *llist_ind(void *key, llist_cmp_t *cmp, LLIST *handle);
LLIST *llist_find(void *key, llist_cmp_t *cmp, LLIST *handle);
void *llist_index_ind(int index, LLIST *handle);

int llist_num(LLIST *handle);

//排序
void llist_sort(llist_cmp_t *cmp, LLIST *handle);
//存储
void llist_store(const char *path, LLIST *handle);
//加载
LLIST *llist_load(const char *path);
//flag = 0 -> next
//flag = 1 -> prev
void llist_travel(LLIST *handle, llist_op_t *op, int flag);//遍历
void llist_destroy(LLIST *handle);//销毁
void llist_clean(LLIST *handle);//清空

#endif /*LLIST_H*/

    

llist.c

#include "llist.h"

LLIST *llist_create(int size)
{
	LLIST *handle = NULL;

	handle = (LLIST *)malloc(sizeof(LLIST));
	ERRP(NULL == handle, malloc handle, goto ERR1);

	handle->head.data = NULL;
	handle->head.next = &handle->head;
	handle->head.prev = &handle->head;//循环双向链表
	handle->size = size;//struct cls_t new->data
	handle->num = 0;

	return handle;
ERR1:
	return NULL;
}

//实现后插
int llist_append(void *data, LLIST *handle)
{
	struct node_t *new = NULL;

	new = (struct node_t *)malloc(sizeof(struct node_t));
	ERRP(NULL == new, malloc new, goto ERR1);

	new->data = (void *)malloc(handle->size);
	ERRP(new->data == NULL, malloc new->data, goto ERR2);

	memcpy(new->data, data, handle->size);
	//用户传递数据保存到节点信息中
	//====================
	//把new节点信息插入到链表handle->head中

	new->next = &handle->head;
	new->prev = handle->head.prev;
	handle->head.prev->next = new;
	handle->head.prev = new;

	handle->num++;

	return 0;
ERR2:
	free(new);
ERR1:
	return -1;
}

//实现头插
int llist_prevend(void *data, LLIST *handle)
{
	//1 接受用户数据data -> int char struct => data 要保存到链表节点信息中。
	//2 定义一个链表节点,用来保存数据 new
	//3 申请空间 new
	//4 new->data 
	//5 数据保存到new->data
	//6 新的节点连接到head(handle)

	struct node_t *new = NULL;

	ERRP(data == NULL, llist_prevend option, goto ERR1);

	new = (struct node_t *)malloc(sizeof(struct node_t));
	ERRP(NULL == new, malloc new, goto ERR1);

	new->data = (void *)malloc(handle->size);
	ERRP(new->data == NULL, malloc new->data, goto ERR2);

	memcpy(new->data, data, handle->size);
	
	new->next = handle->head.next;
	new->prev = &handle->head;
	handle->head.next->prev = new;
	handle->head.next = new;
	handle->num++;

	return 0;
ERR2:
	free(new);
ERR1:
	return -1;
}

//指定位置插入
int llist_index_insert(int index, void *data, LLIST *handle)
{
	struct node_t *tail = NULL;

	ERRP(index < 0 || index > handle->num, llist_index_insert option_1, goto ERR1);

	ERRP(data == NULL, llist_index_insert option_2, goto ERR1);

	struct node_t *new = NULL;

	new = (struct node_t *)malloc(sizeof(struct node_t));
	ERRP(NULL == new, malloc new, goto ERR1);

	new->data = (void *)malloc(handle->size);
	ERRP(new->data == NULL, malloc new->data, goto ERR2);

	memcpy(new->data, data, handle->size);
	
	for (tail = &handle->head; tail->next != &handle->head && index--; tail = tail->next)
		;	
	
	//新的节点添加到tail后面
	new->next = tail->next;
	new->prev = tail;
	tail->next->prev = new;
	tail->next = new;
	handle->num++;
	
	return 0;
ERR2:
	free(new);
ERR1:
	return -1;
}

//删除第一个满足条件
int llist_del(void *key, llist_cmp_t *cmp, LLIST *handle)
{
	struct node_t *tail = NULL;

	for (tail = handle->head.next; tail != &handle->head; tail = tail->next)
	{
		if (!cmp(key, tail->data))
		{
			tail->next->prev = tail->prev;
			tail->prev->next = tail->next;
			free(tail->data);
			free(tail);
			handle->num--;

			return 0;
		}
	}
	return -1;
}
//指定位置删除
int llist_index_del(int index, LLIST *handle)
{
		
	struct node_t *tail = NULL;

	ERRP(index < 0 || index >= handle->num, llist_index_insert option_1, goto ERR1);

	for (tail = handle->head.next; tail != &handle->head && index--; tail = tail->next)
		;	
	
	tail->next->prev = tail->prev;
	tail->prev->next = tail->next;
	free(tail->data);
	free(tail);
	handle->num--;
	
	return 0;
ERR1:
	return -1;
}

//删除所有
int llist_all_del(void *key, llist_cmp_t *cmp, LLIST *handle)
{
	struct node_t *tail = NULL;
	struct node_t *save = NULL;

	for (tail = handle->head.next; tail != &handle->head; tail = save)
	{
		save = tail->next;
		if (!cmp(key, tail->data))
		{
			//tail
			tail->next->prev = tail->prev;
			tail->prev->next = tail->next;
			free(tail->data);
			free(tail);
			handle->num--;
		}
	}
	return 0;
}

//按关键字查找
void *llist_ind(void *key, llist_cmp_t *cmp, LLIST *handle)
{
	struct node_t *tail = NULL;

	for (tail = handle->head.next; tail != &handle->head; tail = tail->next)
	{
		if (!cmp(key, tail->data))
		{
			return tail->data;
		}
	}

	return NULL;
}
//按位置查找
void *llist_index_ind(int index, LLIST *handle)
{
	struct node_t *tail = NULL;

	ERRP(index < 0 || index >= handle->num, llist_index_insert option_1, goto ERR1);

	for (tail = handle->head.next; tail != &handle->head && index--; tail = tail->next)
		;
	return tail->data;
ERR1:
	return NULL;
}

//int llist_num(LLIST *handle)
//{
//	return handle->num;
//}
//查找所有匹配信息
LLIST *llist_find(void *key, llist_cmp_t *cmp, LLIST *handle)
{
	LLIST *ind = NULL;
	struct node_t *tail = NULL;

	ind = llist_create(handle->size);
	ERRP(ind == NULL, llist_create, goto ERR1);

	for (tail = handle->head.next; tail != &handle->head; tail = tail->next)
	{
		if (cmp(key, tail->data) == 0)
		{
			llist_append(tail->data, ind);
		}
	}
	return ind;
ERR1:
	return NULL;
}

//排序
void llist_sort(llist_cmp_t *cmp, LLIST *handle)
{
	struct node_t *val1 = NULL, *val2 = NULL;
	void *tmp = NULL;

	tmp = (void *)malloc(handle->size);

	for (val1 = handle->head.next; val1 != &handle->head; val1 = val1->next)
	{
		for (val2 = val1->next; val2 != &handle->head; val2 = val2->next)
		{
			if (cmp(val1->data, val2->data) > 0)
			{
			#if 0
				tmp = val1->data;
				val1->data = val2->data;
				val2->data = tmp;
			#else
				memmove(tmp, val1->data, handle->size);
				memmove(val1->data, val2->data, handle->size);
				memmove(val2->data, tmp, handle->size);
			#endif
			}
		}
	}
	free(tmp);
}

//存储
void llist_store(const char *path, LLIST *handle)
{
	FILE *fp = NULL;
	int ret;
	struct node_t *tail = NULL;


	fp = fopen(path, "w");
	ERRP(NULL == fp, fopen, goto ERR1);

	//保存数据类型
	ret = fwrite(&handle->size, sizeof(handle->size), 1, fp);
	ERRP(ret != 1, fwrite size, goto ERR2);

	//保存数据大小
	ret = fwrite(&handle->num, sizeof(handle->num), 1, fp);
	ERRP(ret != 1, fwrite num, goto ERR2);

	for (tail = handle->head.next; tail != &handle->head; tail = tail->next)
	{
		fwrite(tail->data, handle->size, 1, fp);
	}
	
	fclose(fp);
	return ;
ERR2:
	fclose(fp);
ERR1:
	return ;
}
//加载
LLIST *llist_load(const char *path)
{
	LLIST *handle = NULL;
	FILE *fp = NULL;
	int size, num;
	int ret, i;
	void *new = NULL;

	fp = fopen(path, "r");
	ERRP(fp == NULL, fopen, goto ERR1);

	ret = fread(&size, sizeof(size), 1, fp);
	ERRP(ret != 1, fread size, goto ERR2);

	ret = fread(&num, sizeof(num), 1, fp);
	ERRP(ret != 1, fread num, goto ERR2);

	handle = llist_create(size);
	ERRP(handle == NULL, llist_create, goto ERR2);

	new = (void *)malloc(size);
	ERRP(new == NULL, malloc new, goto ERR3);

	for (i = 0; i < num; i++)
	{
		ret = fread(new, size, 1, fp);
		ERRP(ret != 1, fread data, goto ERR4);

		ERRP(llist_append(new, handle) == -1, llist_aapend, goto ERR4);
	}

	fclose(fp);
	return handle;
ERR4:
	free(new);
ERR3:
	free(handle);
ERR2:
	fclose(fp);
ERR1:
	return NULL;
}

void llist_travel(LLIST *handle, llist_op_t *op, int flag) //遍历整个链表
{
	struct node_t *tail = NULL;

	if (!flag) //逆序
	{
		//next
		for (tail = handle->head.next; tail != &handle->head; tail = tail->next) 
		{
			op(tail->data);//打印
		}
	}
	else
	{
		for (tail = handle->head.prev; tail != &handle->head; tail = tail->prev)
		{
			op(tail->data);//打印
		}
	}
}

void llist_destroy(LLIST *handle) //销毁链表
{
	struct node_t *tail = NULL;
	struct node_t *save = NULL;

	for (tail = handle->head.next; tail != &handle->head; tail = save)
	{
		save = tail->next;
		free(tail->data);
		free(tail);
	}
	free(handle);
}

void llist_clean(LLIST *handle) //置空链表
{
	struct node_t *tail = NULL, *save = NULL;
	
	for (tail = handle->head.next; tail = &handle->head; tail = save)
	{
		save = tail->next;
		memset(tail->data, 0, handle->size);
		free(tail->data);
		free(tail);
	}
	
	memset(handle, 0, sizeof(handle));
	handle->head.next = NULL;
	handle->head.prev = NULL;

	free(handle);

}

main.c

#include "llist.h"

#define MAX 10

struct cls_t{
    char name[128];
    int id;
    int age;
    char sex;
};
int sort_id(void *d1, void *d2)
{
    return ((struct cls_t *)d1)->id - ((struct cls_t *)d2)->id;
}
int sort_age(void *d1, void *d2)
{
    return ((struct cls_t *)d1)->age - ((struct cls_t *)d2)->age;
}
int cmp_name(void *d1, void *d2)
{
    return strcmp((char *)d1, ((struct cls_t *)d2)->name);
}
int cmp_age(void *d1, void *d2)
{
    return *(int *)d1 - ((struct cls_t *)d2)->age;
}
int cmp_sex(void *d1, void *d2)
{
    return *(char *)d1 - ((struct cls_t *)d2)->sex;
}
void ls(void *data)
{
    struct cls_t *stu = (struct cls_t *)data;

    printf("name : %6s | id : %d | age : %d | sex : %c\n",stu->name, stu->id, stu->age, stu->sex);
}
int main(int argc, char **argv)
{
    int i;
    struct cls_t cls, stu = {"tom", 1001111, 20, 'M'};
    int age = 21, index, bit;
    char sex = 'M';
    char name[128];
    LLIST *handle = NULL;
    struct cls_t *p = NULL;

    if (argc >= 2)
    {
        printf("llist_load!\n");
        handle = llist_load("./stu.db");
        llist_travel(handle, ls, 0);
    }
    else
    {
        handle = llist_create(sizeof(struct cls_t));
        ERRP(handle == NULL, llist_create, goto ERR1);

        for (i = 0; i < MAX; i++)
        {
            snprintf(cls.name, sizeof(cls.name), "stu_%c%c", rand() % 26 + 'A', rand() % 26 + 'a');
            cls.id = rand() % 100 + 1001000;
            cls.age = rand() % 43 + 19;
            cls.sex = "MF"[rand() % 2];

            /*llist_append(&cls, handle);*/
            llist_prevend(&cls, handle);
        }


        llist_travel(handle, ls, 0);
        printf("=======after sort =========\n");
        llist_sort(sort_age, handle);
        printf("sizeof(struct cls_t) = %ld\n", sizeof(struct cls_t));
        llist_store("./stu.db", handle);
        //printf("please input bit : ");
        //scanf("%d", &bit);
        //p = llist_index_ind(bit, handle);
        //GETLINES("please input ind name : ", name);
        //llist_del(name, cmp_name, handle);
        //p = llist_find(name, cmp_name, handle);
        //if (p == NULL)
        //{
        //    printf("no find!\n");
        //}
        //else
        //{
        //    ls(p);
        //}
        //
        //printf("===============\n");
        /*llist_index_insert(10, &stu, handle);*/

        llist_travel(handle, ls, 0);
        /*
         *printf("please input del age : ");
         *scanf("%d", &age);
         */
        /*llist_all_del(&sex, cmp_sex,  handle);*/
        /*llist_travel(llist_find(&sex, cmp_sex, handle), ls, 0);*/
        /*llist_travel(llist_find(&age, cmp_age, handle), ls, 0);*/

        /*
         *printf("============\n");
         *llist_travel(handle, ls, 1);
         */
    }
    llist_destroy(handle);

    return 0;
ERR1:
    return -1;
}


Makefile

CC = gcc
list : llist.o main.o
    $(CC) -o $@ $^

.PHONY:clean
clean:
    @rm -fr *.o list

运行结果(仅打印age排序结果, 但是所有函数尽可能检查安全性的前提下测试OK)


刚才我测试在另外一台机器上复制代码下来编译有点小问题,   llist.h的两个测试宏 注意 '\' 后不能有空格,手动删除一下就完全OK了

猜你喜欢

转载自blog.csdn.net/weixin_38176039/article/details/80464496