链表能够说是一种最为基础的数据结构了,而单向链表更是基础中的基础。链表是由一组元素以特定的顺序组合或连接在一块儿的,不一样元素之间在逻辑上相邻,可是在物理上并不必定相邻。在维护一组数据集合时,就可使用链表,这一点和数组很类似。可是,链表有着数组所不具备的优点。一方面,链表在执行插入删除操做时拥有更高的效率;另外一方面,链表是在堆区动态的开辟存储空间,而大多数的数据在编译时大小并不能肯定,所以这种动态开辟空间的特性也能够说是链表的一个优势。node
就像图像所示,单向链表各个元素之间经过一个指针前后连接起来。每一个元素包含两个部分,分别是数据域和指针域。前一个元素经过next指针指向后一个元素,链表的开始的元素为链表头,即head指针所指,链表结束的元素为链表尾,尾部元素的next指针指向NULL。可见,单向链表为线性结构。数组
若想访问链表中的一个元素,咱们只能从链表的头部开始,顺着指针指向逐查找。若是从链表头移动到指定的元素,而这时候咱们又想访问当前元素以前的某个元素,这时候只能从头在次遍历链表。这相比数组可以经过下标直接访问要麻烦的多,不过咱们应该根据不一样的应用场景选择数组仍是链表,它们只有在对的地方才能发挥出巨大的威力。数据结构
typedef struct node//链表元素的结构 { void *data;//节点中的数据域,设置为无类型指针,数据类型大小由使用者定义 struct node *next;//指向下一节点的指针 }Node; typedef struct list//链表的结构 { int size;//链表中节点个数 void (*destroy)(void *data);//因为链表节点中的数据是用户自定义的,故须要调用者提供释放空间的函数 void (*print_data)(const void *data);//同,由用户自定义打印数据的函数 int (*match)(const void *key1, const void *key2);//同,由用户自定义数据的比较方式 Node *head;//记录链表的头部位置 Node *tail;//记录链表的尾部位置 }List;
示意图以下:
函数
下面是链表操做函数的接口,以及简单介绍:优化
extern void list_init(List *list, void (*destroy)(void *data), void (*print_data)(const void *data), \ int (*match)(const void *key1, const void *key2));//初始化一个链表 extern int list_ins_head(List *list, const void *data);//链表的插入,将节点从头部插入 extern int list_ins_tail(List *list, const void *data);//链表的插入,将节点从尾部插入 extern int list_ins_sort(List *list, const void *data);//链表的插入,插入后链表是一个有序的链表 extern void* list_search(List *list, const void *data);//在链表中查找指定数据,若找到返回数据的地址 extern void* list_remove(List *list, const void *data);//在链表中删除指定数据,若找到删除节点并将数据地址返回 extern void list_reverse(List *list);//将链表逆置 extern void list_sort(List *list);//将链表按照必定方式排序 extern void print_list(List *list);//打印链表 extern void list_destroy(List *list);//删除整个链表 #define list_size(list) (list->size) //返回链表节点个数
使用list_init函数初始化一个链表,以便链表的其余操做。3d
void list_init(List *list, void (*destroy)(void *data), void (*print_data)(const void *data), \ int (*match)(const void *key1, const void *key2)) { list->size = 0;//初始时,链表没有节点,设置为0 list->head = NULL;//头和尾置空 list->tail = NULL; list->match = match;//初始化链表的成员函数 list->destroy = destroy; list->print_data = print_data; return; }
使用list_ins_head函数,在链表的头部插入数据。示意图以下:指针
从示意图能够看出,单向链表的部插入逻辑很是简单。仔细观察,标绿的部分代码有重复,能够优化code
/*在链表的头部插入数据*/ int list_ins_head(List *list, const void *data) { Node *new_node = (Node *)calloc(1, sizeof (Node)); //建立插入的节点 if(new_node == NULL) return -1; new_node->data = (void *)data;//关联节点与数据 /* if(list_size(list) == 0)//链表为空时,插入节点 { list->tail = new_node; new_node->next = NULL; list->head = new_node; } else //链表非空时将节点插入头部 { new_node->next = list->head; list->head = new_node; } */ if(list_size(list) == 0)//链表为空时,插入节点 list->tail = new_node; new_node->next = list->head; list->head = new_node; list->size ++;//维护链表size属性 return 0; }
使用list_ins_tail函数,在链表的尾部插入数据,示意图以下:blog
/*在链表的尾部插入数据*/ int list_ins_tail(List *list, const void *data) { Node *new_node = (Node *)calloc(1, sizeof (Node)); //建立插入的节点 if(new_node == NULL) return -1; new_node->data = (void *)data;//关联节点与数据 if(list_size(list) == 0) list->head = new_node; else list->tail->next = new_node; list->tail = new_node; new_node->next = NULL; list->size ++; return 0; }
使用list_ins_sort函数,进行链表的有序插入。排序
链表的有序插入大体能够分为两种状况:
其一,链表为空时直接插入;
其二,链表非空时,在此时又分为三种小状况;
链表为空时,操做方法和头尾部插入相似。链表非空时,咱们须要先寻找到插入位置,而后在将数据插入链表。
在此以前咱们已经了解了如何在链表的头部和尾部插入元素,那么,如今惟一须要处理的即是 在链表中部插入节点 ,这是链表插入操做的核心。
注意:在链表中部插入节点时,必须获得前一节点的位置,即图中指向蓝色节点的指针p_pre.
插入节点的逻辑了解后,处理在非空链表状况下插入节点就清晰多了。
/*在链表的有序插入数据*/ int list_ins_sort(List *list, const void *data) { Node *new_node = (Node *)calloc(1, sizeof (Node)); //建立插入的节点 if(new_node == NULL) return -1; new_node->data = (void *)data;//关联节点与数据 if(list_size(list) == 0)//链表为空时,插入节点 { list->tail = new_node; new_node->next = NULL; list->head = new_node; } else//链表非空 { Node *p_cur = list->head; Node *p_pre = list->head; while(p_cur != NULL && list->match(new_node->data, p_cur->data) > 0)//查找链表的插入位置 { p_pre = p_cur; p_cur = p_cur->next; } if(p_cur != NULL)//插入位置在头部和中间时 { if(p_cur == list->head)//插入位置在头部 { new_node->next = list->head; list->head = new_node; } else//位置在链表中间 { new_node->next = p_pre->next; p_pre->next = new_node; } } else//插入位置在链表尾部 { list->tail->next = new_node; list->tail = new_node; new_node = NULL; } } list->size ++; return 0; }
使用list_search函数,查找链表中与数据匹配的节点,并返回节点指针。
此处查找逻辑与list_ins_sort中的查找逻辑基本相似,不作赘述。
/*查找链表中与数据匹配的节点,并返回节点指针*/ void* list_search(List *list, const void *data) { if(list_size(list) == 0) { printf("list is empty\n"); return NULL; } else { Node *p_cur = list->head; while(p_cur != NULL && list->match(p_cur->data, data) != 0)//查找数据在链表中的位置 p_cur = p_cur->next; if(p_cur != NULL)//找到返回数据地址,不然返回NULL return p_cur->data; else return NULL; } }
使用list_remove函数,删除节点,并将节点中的数据返回,交由用户处理。
此处查找逻辑与list_ins_sort中的查找逻辑基本相似,不作赘述。
和插入节点中分为头部、中部尾部相似,删除也分为头中尾部。
删除头部节点
不过删除头部节点时须要注意一点,就是当链表仅有一个节点时,咱们须要维护一下tail指针。
删除中部节点
删除尾部节点
注意:代码中将中部与尾部的删除进行了合并。
/*删除指定数据的节点*/ void* list_remove(List *list, const void *data) { void *old_data = NULL; Node *p_cur = list->head; Node *p_pre = list->head; while (p_cur != NULL && list->match(p_cur->data, data) !=0) { p_pre = p_cur; p_cur = p_cur->next; } if(p_cur != NULL && list->match(p_cur->data, data) ==0)//删除位置在头部和中间时 { if(p_cur == list->head)//删除位置在头部 { list->head = p_cur->next; if(p_cur->next == NULL) list->tail = NULL; } else//中部时或尾部 { p_pre->next = p_cur->next; if(p_cur->next == NULL)//判断是否为尾部 list->tail = p_pre; } old_data = p_cur->data; free(p_cur); list->size --; } return old_data; }
使用list_reverse函数将链表逆置。
逆置过程:
观察能够发现,逆置的过程本质上就是将原来的链表逐个摘下头节点,插入逆置后的链表的头部
void list_reverse(List *list) { if(list_size(list) != 0) { Node *p_pre = list->head; Node *p_cur = list->head->next; list->head->next = NULL; list->tail = list->head; while(p_cur!= NULL) { p_pre = p_cur; p_cur = p_cur->next; p_pre->next = list->head; list->head = p_pre; } } return; }
使用list_sort函数对链表进行排序,此处使用选择排序法。
void list_sort(List *list) { if(list_size(list) != 0) { Node *p_i = list->head; while(p_i->next != NULL) { Node *p_min = p_i; Node *p_j = p_min->next; while (p_j != NULL) { if(list->match(p_min->data, p_j->data) > 0) p_min = p_j; p_j = p_j->next; } if(p_min != p_i) { void *data = p_i->data; p_i->data = p_min->data; p_min->data = data; } p_i = p_i->next; } } return; }
void print_list(List *list) { if(list->head == NULL)//链表为空 { printf("list is empty\n"); } else //链表非空 { Node * p_cur = list->head; while (p_cur) { list->print_data(p_cur->data); p_cur = p_cur->next; } } return; }
void list_destroy(List *list) { Node *p_cur = list->head; while (p_cur != NULL) { list->head = list->head->next; list->destroy(p_cur->data);//释放节点中的数据 free(p_cur);//释放节点 p_cur = list->head; } memset(list, 0, sizeof (List)); return; }