zoukankan      html  css  js  c++  java
  • 数据结构单链表

    算法和数据结构总结---单链表

    链表可以说是一种最基本的数据结构,链表通常以一种特定的组合将元素链接在一起,以便可以对元素实现方便的管理维护。这一点和我们常常使用的数组很相似,但是链表在最多的情况下可以带来比链表更为优势的操作,链表通常是在系统需要的时候动态开辟的,换句话说链表的存储空间是在程序运行的时候在进行分配的,这就说明链表的长度是可变的。在许多的时候,我们无法明确的确定数据的大小直接在编译前分配内存,这种动态分配的方法也是链表的优势之一。

    单链表的定义

    单链表(通常也被成为链表) 链表元素由彼此的内部的一个指针相链接。每个元素包括两个成员:数据成员和一个称为next的指针成员,每个元素通过next指针指向下一个链表元素,实现链表的链接。链表的开始处称之为链表的 “头”,链表的最后结束部分称为链表的 “尾”。单链表只允许一个一个方向遍历链表,尽管有的时候我们保存链表的指针信息。下图为一个标准的单链表结构。
    单链表图

    图1 标准的单链表逻辑模型
    从物理内存上在看,链表不是连续的内存存储,但是在逻辑上可以理解链表是线性的。我们可以类似的画出链表的内存存储方式:

    image

    图2 标准的单链表内存模型
    元素和元素之间的连接关系只是为了可以确保所有的元素可以访问的到,倒是万事万物不可能是十全十美的,你的优点有多强,你的弱点就有多弱。如果我们错误的丢失一个节点,则从这一节点后的所有元素均无法访问的到。

    单链表接口的公共接口

    在实现一个链表,我们先定义一个链表需要哪些操作进行实现。

    • void list_init(List* list, void (*destroy)(void* data));

      • 返回值:无
      • 描述:这个是单链表初始化函数,必须在链表进行其他操作其使用,List* list是单链表的头尾信息结构体。当调用list_destroy时,destroy参数提供一种释放动态内存的方法。当链表中的元素有动态分配的内存,在摧毁链表时必须free掉分配的动态内存。destroy作为一个用户自己可以设置的析构函数,提高了链表的稳定性和灵活性,如果链表当中不存在应该释放的动态内存,destroy的值应该为NULL
      • 时间复杂度:O(1) 在链表初始化时,时间是固定的。
    • void list_destroy(List* list)

      • 返回值:无
      • 描述:销毁链表list,在销毁后不可以对list进行任何的数组操作,除非重新在对数组进行初始化操作,如果传给list_init函数的形参destroy不是NULL的话,则每次移除链表元素都要执行该函数一次。
      • 时间复杂度:O(n) n代表链表中元素的个数。
    • int list_ins_next(List* list, ListElement* element, const void* data);

      • 返回值:如果成功插入链表返回0,出现错误返回-1
      • 描述:将元素插入list指向的单向链表的element元素之后,如果elementNULL,则新元素插入链表的头部。新元素包含一个指向data的指针。
      • 时间复杂度:O(1)
    • int list_rem_next(List* list, ListElement* element, void** data);

      • 返回值:如果返回值成功则返回0,出现错误则返回-1
      • 描述:函数的功能是移除,如果element 等于 NULL 则移除头元素。调回返回后 data 指向已移除的那个链表元素的数据,由用户可以灵活的使用data的存储空间。
      • 复杂度:O(1) 移除链表元素的时间是固定的
    • 宏定义列表

      • #define list_size(list) ((list)->size)
      • #define list_tail(list) ((list)->tail)
      • #define list_is_head(list,element) ((element) == (list)->head ? 1 : 0)
      • #define list_is_tail(element) ((element)->next == NULL ? 1 : 0)
      • #define list_data(element) ((element)->data)
      • #define list_next(element) ((element)->next)
      • 复杂度:O(1) 宏定义的时间都是固定的,利用宏定义来命名为了提高代码的可读性

    单链表的头文件list.h

    //list.h
    #pragma 
    #ifndef LIST_H
    #define LIST_H
    #include <stdlib.h>
    
    //Define a structure for list element
    typedef struct ListElement_
    {
    	void*			data;    
    	struct		    ListElement_* next;
    }ListElement;
    //Define a structure for linked element
    typedef struct List_
    {
    	int				size;
    	int				(*match)(const void* key1, const void* key2);
    	void			(*destroy)(void* data);
    	ListElement*	head;
    	ListElement*    tail;
    	
    } List;
    
    // Public Interface
    void list_init(List* list, void	(*destroy)(void* data));
    void list_destroy(List* list);
    int  list_ins_next(List* list, ListElement* element, const void* data);
    int  list_rem_next(List* list, ListElement* element, void** data);
    
    #define list_size(list) ((list)->size)
    #define list_tail(list) ((list)->tail)
    #define list_is_head(list,element) ((element) == (list)->head ? 1 : 0)
    #define list_is_tail(element) ((element)->next == NULL ? 1 : 0)
    #define list_data(element) ((element)->data)
    #define list_next(element) ((element)->next)
    #endif
    

    单链表函数的实现原理

    链表初始化

    void list_init(List* list, void (*destroy)(void* data));


    链表初始化的操作很简单,只要创造一个空链表就可以,将存储链表头和尾信息的结果体置
    为空指针,链表的长度size为0。链表的析构函数置为我们指定的析构函数。


    void list_init(List* list, void	(*destroy)(void* data)) 
    {
    	list->size = 0;
    	list->destroy = destroy;
    	list->head = NULL;
    	list->tail = NULL;
    	return;
    }
    
    链表摧毁

    void list_destroy(List* list)


    void list_destroy(List* list) 函数用于销毁链表,其作用是要移除链表中所有的元素,如果链表初始化时list中的destroy参数不为0,则表示在移走每个链表元素时还必须对链表元素的数据进行处理,所以思路为当链表的长度不为0和析构函数的指针不是空指针时,不停的移走头链表,并返回移除链表的数据指针通过析构函数对数据释放,最后在list的区域清理干净。


    void list_destroy(List* list) 
    {
    	void* data;
    	// 移走每个链表元素
    	while (list_size(list) > 0)
    	{
    		if (list_rem_next(list,NULL,(void**)&data) == 0 && list->destroy != NULL) 
    		{
    			list->destroy(data);
    		}
    	}
    	memset(list, 0, sizeof(list));
    	return;
    }
    
    
    链表后插

    int list_ins_next(List* list, ListElement* element, const void* data)


    链表后插的操作很简单,插入就两种大情况要分类讨论,一种是当int list_ins_next(List* list, ListElement* element, const void* data)中的element元素为NULL是表示插入头接单,另一种就是在一个链表元素后插。

    1. element 为 0 插入头链表。当整个链表不存在链表元素时,这个时候链表头即是链表为链表尾,
      这个时候要更新listtail信息。
      当整个链表存在链表元素时候,这个时候新的链表就取代原来的链表头,list的链表头信息就更新。
    2. element 不为 0 就要注意你插入的元素是不是链表尾,如果是链表尾,则要更新list的链表尾指针。否则就让新链表的的next指针指向elementnetx指针指向的链表,elementnext指针指向新的链表元素。
      这整个过程如图3所示:
      最后整个list的长度要加1;

    在这里插入图片描述

    图3 单链表的插入过程
    int  list_ins_next(List* list, ListElement* element, const void* data)
    {
    	ListElement* new_element;
    	if ((new_element = (ListElement*)malloc(sizeof(ListElement)))== NULL)
    	{
    		return -1;
    	}
    	new_element->data = (void*)data;
    	if (element == NULL)
    	{
    		if (list_size(list) == 0)
    		{
    			list->tail = new_element;
    		}
    		new_element->next = list->head;
    		list->head = new_element;
    	}
    	else 
    	{
    		if (element->next == NULL)
    		{
    			list->tail = new_element;
    		}
    		new_element->next = element->next;
    		element->next = new_element;
    	}
    	list->size++;
    	return 0;
    }
    
    链表后删

    int list_rem_next(List* list, ListElement* element, void** data)


    这个函数的功能就是要移除int list_rem_next(List* list, ListElement* element, void** data)element元素后的一个元素链表,并不是元素释放,这一点要清楚。和插入链表的操作相同这个也要分类两种情况进行分析:移除头结点和其他结点。

    1. element 为 0 移除头链表。先把头结点的数据指针位置保持,并用old_element保持要移除的链表的空间内存地址,这个时候就用原本头结点的下一链表取代原本的头结点。如果这个时候链表的长度为1。
    2. 如果element的下一个元素就是空指针,那不能删,返回错误,否则把element的下一个链表的数据空间和链表位置保存。利用element->next = element->next->next;更新链表结构。如果删掉是最后一个链表结点,要注意更新链表信息的尾信息。

    最后整个list的长度要减1;


    在这里插入图片描述

    图4 单链表的后删过程
    int  list_rem_next(List* list, ListElement* element, void** data) 
    {   
    	ListElement* old_element;
    	if (list_size(list) == 0)
    	{
    		return -1;
    	}
    	if (element == NULL)
    	{
    		*data = list->head->data;
    		old_element = list->head;
    		list->head = list->head->next;
    
    		if (list->size == 1)
    		{
    			list->tail = NULL;
    		}
    	}
    	else 
    	{
    		if (element->next == NULL)
    		{
    			return -1;
    		}
    		*data = element->next->data;
    		old_element = element->next;
    		element->next = element->next->next;
    		if (element->next == NULL)
    		{
    			list->tail = element;
    		}
    	}
    
    	free(old_element);
    	list->size--;
    	return 0;
    }
    

    完整代码

    //list.h
    #pragma 
    #ifndef LIST_H
    #define LIST_H
    #include <stdlib.h>
    
    //Define a structure for list element
    typedef struct ListElement_
    {
    	void*			data;    
    	struct		    ListElement_* next;
    }ListElement;
    //Define a structure for linked element
    typedef struct List_
    {
    	int				size;
    	int				(*match)(const void* key1, const void* key2);
    	void			(*destroy)(void* data);
    	ListElement*	head;
    	ListElement*    tail;
    	
    } List;
    
    // Public Interface
    void list_init(List* list, void	(*destroy)(void* data));
    void list_destroy(List* list);
    int  list_ins_next(List* list, ListElement* element, const void* data);
    int  list_rem_next(List* list, ListElement* element, void** data);
    
    #define list_head(list) ((list)->head)
    #define list_size(list) ((list)->size)
    #define list_tail(list) ((list)->tail)
    #define list_is_head(list,element) ((element) == (list)->head ? 1 : 0)
    #define list_is_tail(element) ((element)->next == NULL ? 1 : 0)
    #define list_data(element) ((element)->data)
    #define list_next(element) ((element)->next)
    #endif
    
    // list.c
    #include <stdio.h>
    #include <stdlib.h>
    #include "list.h"
    #include <string.h>
    
    /*
    void list_init(List* list, void	(*destroy)(void* data))
    list_init 链表初始化
    链表初始化只需要把链表的size成员设置为0,把函数指针成员设置为析构函数函数
    */
    void list_init(List* list, void	(*destroy)(void* data)) 
    {
    	list->size = 0;
    	list->destroy = destroy;
    	list->head = NULL;
    	list->tail = NULL;
    	return;
    }
    
    /*
    void list_destroy(List* list);
    链表摧毁函数,功能就是摧毁链表中的全部元素,如果调用list_init时
    destroy的参数不为NULL,则当每个元素被移除的时候都将调用list_destroy
    一次
    */
    void list_destroy(List* list) 
    {
    	void* data;
    	// Remove each element
    	while (list_size(list) > 0)
    	{
    		if (list_rem_next(list,NULL,(void**)&data) == 0 && list->destroy != NULL) 
    		{
    			list->destroy(data);
    		}
    	}
    	memset(list, 0, sizeof(list));
    	return;
    }
    
    /*
    int  list_ins_next(List* list, ListElement* element, const void* data);
    */
    
    int  list_ins_next(List* list, ListElement* element, const void* data)
    {
    	ListElement* new_element;
    	if ((new_element = (ListElement*)malloc(sizeof(ListElement)))== NULL)
    	{
    		return -1;
    	}
    	new_element->data = (void*)data;
    	if (element == NULL)
    	{
    		if (list_size(list) == 0)
    		{
    			list->tail = new_element;
    		}
    		new_element->next = list->head;
    		list->head = new_element;
    	}
    	else 
    	{
    		if (element->next == NULL)
    		{
    			list->tail = new_element;
    		}
    		new_element->next = element->next;
    		element->next = element;
    	}
    	list->size++;
    	return 0;
    }
    /*
    int  list_rem_next(List* list, ListElement* element, void* data)
    */
    int  list_rem_next(List* list, ListElement* element, void** data) 
    {   
    	ListElement* old_element;
    	if (list_size(list) == 0)
    	{
    		return -1;
    	}
    	if (element == NULL)
    	{
    		*data = list->head->data;
    		old_element = list->head;
    		list->head = list->head->next;
    
    		if (list->size == 1)
    		{
    			list->tail = NULL;
    		}
    	}
    	else 
    	{
    		if (element->next == NULL)
    		{
    			return -1;
    		}
    		*data = element->next->data;
    		old_element = element->next;
    		element->next = element->next->next;
    		if (element->next == NULL)
    		{
    			list->tail = element;
    		}
    	}
    
    	free(old_element);
    	list->size--;
    	return 0;
    }
    
    
    
  • 相关阅读:
    《Linux命令行与shell脚本编程大全 第3版》Linux命令行---37
    《Linux命令行与shell脚本编程大全 第3版》Linux命令行---36
    《Linux命令行与shell脚本编程大全 第3版》Linux命令行---35
    《Linux命令行与shell脚本编程大全 第3版》Linux命令行---34
    《Linux命令行与shell脚本编程大全 第3版》Linux命令行---33
    scrollBarStyle- listview滑动条调整
    mk-编译信息的意义
    values-dimen 不同分辨率资源实现引用
    ubuntu-虚拟机分辨率设定
    vmware-虚拟机播放器的下载、安装
  • 原文地址:https://www.cnblogs.com/Kroner/p/14766160.html
Copyright © 2011-2022 走看看