引言
在上一篇博客数据结构1.单链表中,我们对链表相对数组的优缺点进行了比较。
而且,我们发现,链表是一种物理存储单元上非连续、非顺序的存储结构,所以,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
若是想要对链表中的其中一个元素进行操作,通常都需要p_node = p_node->next;
的循环进行。
计算机科学领域的任何问题,都可以通过增加一个间接中间层来解决。
Any problems in computer science can be soved by another layer of indirection.
所以,我们在定义这个链表的时候,可以给链表加上一层控制信息。如下:
* 本文的所有源程序可以在single_control_list上得到。*
一、链表数据类型的定义
/*链表数据类型定义*/
struct List_node; //声明一个类型
//链表控制信息
typedef struct List
{
struct List_node *head; //指向链表头部
struct List_node *tail; //指向链表尾部
int count; //链表节点数量
}List;
//链表节点信息
typedef struct List_node
{
int data; //数据域
struct List_node *next; //指针域
}List_node;
这样我们可以看到,已经可以直接获取到链表节点个数以及指向尾部节点的指针。我们可以用来做什么呢?
二、链表的接口:
/********链表的接口********/
List *init_list(void) ; //1. 链表的初始化
void destroy_list(List **list) ; //2. 链表的销毁
Boolean push_front(List *list, int value) ; //3.1 头部插入
Boolean push_back(List *list, int value) ; //3.2 尾部插入
Boolean pop_front(List *list) ; //4.1 头部删除
Boolean pop_back(List *list) ; //4.2 尾部删除
List_node *find_node(List *list, int value) ; //5.1 链表的查找
List_node *find_revise_node(List *list, int num) ; //5.2 找到链表的倒数第num个节点
List_node *find_mid_node(List *list) ; //5.3 找到链表的中间节点
void modify_node(List *list, int value) ; //6.1 链表节点的修改
Boolean insert_node(List *list, int index, int value) ; //6.2 链表节点的插入(下标)
void delete_one_node(List *list, List_node *node) ; //6.3 在O(1)的时间复杂度删除节点
void sort_list_ascend(List *list) ; //7.1 升序
void sort_list_descent(List *list) ; //7.2 降序排列
void show_list(List *list) ; //8.1 显示链表信息
void reverse_show_list(List *list) ; //8.2 逆序输出链表信息
int get_list_count(List *list) ; //9. 得到链表节点数量
List *merge_two_lists(List *list1, List *list2) ; //10. 合并两个有序链表
List *merge_two_lists_recure(List *list1, List *list2); //11. 合并两个有序链表(递归)
List *reverse_list(List *list) ; //12. 逆置一个链表
List *list_dump(List *list) ; //13. 链表的拷贝
Boolean is_list_intersect(List *list1, List *list2) ; //14. 判断链表是否有交点
List_node *get_first_common_node(List *list1, List *list2) ; //15. 得到第一个交点
Boolean has_circle(List *list) ; //16. 判断一个链表是否有环
List_node *find_circle_first_node(List *list) ; //17. 找到带环链表的环入口节点
我们将这些都放入ctl_list.h文件当中,包括链表的定义,链表的接口,一些用到的宏定义。
为了能够方便的进行程序的设计,我们仍旧采用包裹函数的方式对某些操作进行简化:
/*接口包裹函数定义*/
static void *Malloc(size_t size);
static List_node *Create_node(void);
static void Swap(void *a, void *b, int length);
static void Rev_show_list(List_node *node);
我们常用到的操作函数定义如下:
/*接口包裹函数实现*/
static void *Malloc(size_t size)
{
void *result = malloc(size);
if(result == NULL){
fprintf(stderr, "the memory is full\n");
exit(1);
}
return result;
}
static List_node *Create_node(void) //创建链表节点
{
List_node *node = (List_node *)Malloc(sizeof(List_node));
bzero(node, sizeof(List_node));
return node;
}
static void Swap(void *a, void *b, int length)
{
void *temp = Malloc(length);
memcpy(temp, a, length);
memcpy(a, b, length);
memcpy(b, temp, length);
free(temp);
}
static void Rev_show_list(List_node *node)
{
//要打印当前节点,先打印其后续节点
if(node != NULL){
rev_show_list(node->next);
printf("%d ", node->data);
}
}
宏定义如下:
/*控制链表宏定义*/
#define TRUE (1)
#define FALSE (0)
#define ZERO (0)
#define ONLY_ONE (1)
#define TWO (2)
#define get_data_size() ((unsigned long)&(((List_node *)0)->next))
三、链表接口的实现
//1.链表的初始化
// | head | tail | count |
// \ / 0
// \ /
// NULL
List *init_list(void)
{
List *list = (List *)Malloc(sizeof(List));
bzero(list, sizeof(List)); //全部清零
return list;
}
//2. 链表的销毁
// list
// /
// list_head
// /
// | head | tail | count |
// \ \ n + 1
//
// \ \
// node->node2->node3
void destroy_list(List **list)
{
if(list == NULL || *list == NULL){
return ;
}
// 链表删除步骤:
// 1.删除链表节点信息;
while((*list)->count){ //头部删除链表节点
pop_front(*list);
}
// 2.删除链表控制信息.
free(*list);
*list = NULL;
}
//3.1 头部插入
Boolean push_front(List *list, int value)
{
List_node *node = NULL;
if(list == NULL){
return FALSE;
}
//创建节点并赋值
node = create_node();
node->data = value;
// case1:
// | head | tail | count |
// \ / 1
// \ /
// node
if(list->count == ZERO){
list->tail = node;
}else{
// case2:
// | head | tail | count |
// \ / n + 1
// \ /
// node->node2->node3
node->next = list->head;
}
list->head = node;
list->count++;
return TRUE;
}
//3.2 尾部插入
Boolean push_back(List *list, int value)
{
List_node *node = NULL;
if(list == NULL){
return FALSE;
}
node = create_node();
node->data = value;
// case1:
// | head | tail | count |
// \ / 1
// \ /
// node
if(list->count == ZERO){
list->head = list->tail = node;
}else{
// case2:
// | head | tail | count |
// \ / n + 1
// \ /
// node1->node2->node
list->tail->next = node;
list->tail = node;
}
list->count++;
return TRUE;
}
//4.1 头部删除
Boolean pop_front(List *list)
{
List_node *p_node = NULL;
if(list == NULL || list->count == ZERO){
return FALSE;
}
// case1:
// | head | tail | count |
// \ / 1->0
// \ /
// node
// case2:
// | head | tail | count |
// \ / n-> n - 1
// \ /
// node1->node2->node3
p_node = list->head;
if(list->count == ONLY_ONE){
list->head = list->tail = NULL;
}else{
list->head = list->head->next;
}
free(p_node);
list->count--;
return TRUE;
}
//4.2 尾部删除
Boolean pop_back(List *list)
{
List_node *p_node = NULL;
if(list == NULL || list->count == ZERO){
return FALSE;
}
// case1:
// | head | tail | count |
// \ / 1->0
// \ /
// node
// case2:
// | head | tail | count |
// \ / n-> n - 1
// \ /
// node1->node2->node3
p_node = list->head;
if(list->count == ONLY_ONE){
list->head = list->tail = NULL;
free(p_node);
}else{
//判断倒数第二个?
// p_node->next == list->tail
while(p_node->next != list->tail){
p_node = p_node->next;
}
free(list->tail);
list->tail = p_node;
p_node->next = NULL;
}
list->count--;
return TRUE;
}
//5.1 链表的查找
List_node *find_node(List *list, int value)
{
if(list == NULL)
{
return NULL;
}
List_node *p = list->head;
while(p != NULL)
{
if(p->data == value)
{
printf("found the value: %d\n", value);
return p;
}
p = p->next;
}
printf("No found!");
return NULL;
}
//5.2 找到链表的倒数第num个节点
List_node *find_revise_node(List *list, int num)
{
List_node *move = NULL;
int move_count = 0;
//move + num == count
if(list == NULL || num <= 0 || num > list->count){
return NULL;
}
move = list->head;
//移动的步长
move_count = list->count - num;
while(move_count--){
move = move->next;
}
return move;
}
//5.3 找到链表的中间节点
// 1.) 快慢指针:
//
// f 每次移动2步, s 每次移动1步
// 10 23 5 15 50 67 45 32 82
//
// 2.) 10 / 2 == 5
// 10 >> 1 == 5
//
// 0000 1010 10
// 10 >> 1
// 0000 0101 5
List_node *find_mid_node(List *list)
{
List_node *move = NULL;
int move_count = 0;
if(list == NULL){
return NULL;
}
move = list->head;
move_count = list->count >> 1;
while(move_count--){
move = move->next;
}
return move;
}
//6.1 链表节点的修改
void modify_node(List *list, int index, int value)
{
int count = index;
List_node *p = list->head;
if(list == NULL || index >= list->count || index < 0)
{
return ;
while(count--)
{
p = p->next;
}
p->data = value;
}
//6.2链表节点的插入
Boolean insert_node(List *list,int index, int value)
{
if(list == NULL || index > list->count || index < 0)
{
return FALSE;
}
//在最后则尾部插入
if(index == list->count)
{
return push_back(list, value);
}
//在最前则头部插入
if(index == 0)
{
return push_front(list, value);
}
int count = index -1;
List_node *p_node = list->head;
//创建新节点
List_node *node = create_node();
node->data = value;
//寻找需插入的节点位置
while(count--)
{
p_node = p_node->next;
}
//进行插入
node->next = p_node->next;
p_node->next = node;
list->count++;
return TRUE;
}
//6.3 在O(1)的时间复杂度删除节点
//由于是单链表,无法获取到前一个节点的位置,
//所以,我们在这里用后一个节点代替我们所需的节点。
void delete_one_node(List *list, List_node *node)
{
if(list == NULL || node == NULL)
{
return ;
}
//尾部删除
if(node == list->tail)
{
pop_back(list);
return ;
}
List_node *p = node->next;//保存下一个节点位置
node->data = node->next->data; //这个节点作为后一个的替代
node->next = node->next->next; //直接指向下一个节点的后面
free(p);
list->count--;
}
//7.1 升序排列
//我们在这里用到了一种新的办法求我们的结构体大小,定义在宏里:
// ((unsigned long)&(((List_node *)0)->next))
//
// (List_node *)0 //指向首地址在0的List_node的指针
// ((List_node *)0)->next) //取next则指向了它的下一个
// &(((List_node *)0)->next)) //取地址,得到的是这个地址,由于首地址,则正好是大小
// ((unsigned long)&(((List_node *)0)->next)) //unsigned long 类型转换,得到就是大小
// sizeof(List_node) - sizeof(List_node *);
//内存分配,结构体里内存对齐现象:
// int data; // 0 1 2 3
// // 4 5 6 7
// struct List_Node *next; // 8 9 10 11
// 12 13 14 15
void sort_list_ascend(List *list)
{
List_node *p_node = NULL;
List_node *q_node = NULL;
unsigned long data_size = 0;
if(list == NULL || list->count < TWO){
return ;
}
data_size = get_data_size(); //求得数据区域得大小
for(p_node = list->head; p_node->next; p_node = p_node->next){
for(q_node = p_node->next; q_node ; q_node = q_node->next){
if(p_node->data > q_node->data){
swap(p_node, q_node, data_size);
}
}
}
}
//7.2 降序排列
//和升序类似
void sort_list_descend(List *list)
{
List_node *p_node = NULL;
List_node *q_node = NULL;
unsigned long data_size = 0;
if(list == NULL || list->count < TWO){
return ;
}
data_size = get_data_size(); //求得数据区域得大小
for(p_node = list->head; p_node->next; p_node = p_node->next){
for(q_node = p_node->next; q_node ; q_node = q_node->next){
if(p_node->data < q_node->data){
swap(p_node, q_node, data_size);
}
}
}
}
//8.1 显示链表信息
void show_list(List *list)
{
List_node *p_node = NULL;
if(list != NULL && list->count != ZERO){
for(p_node = list->head; p_node; p_node = p_node->next){
printf("%d ", p_node->data);
}
printf("\n");
}
}
//8.2 逆序输出链表信息
void reverse_show_list(List *list)
{
if(list == NULL || list->count == ZERO){
return ;
}
// .h中定义的递归(先递归再输出)
Rev_show_list(list->head);
printf("\n");
}
//9 得到链表节点数量
//这是一个有控制信息的链表,有关于节点数量的变量
int get_list_count(List *list)
{
if(list == NULL){
return -1;
}
return list->count;
}
///10 合并两个有序链表
//l1 | head | tail | count |
// \ / n-> n - 1
// \ /
// node1->node2->node3
//
//
//l2 | head | tail | count |
// \ / n-> n - 1
// \ /
// node1->node2->node3
//
List *merge_two_lists(List *list1, List *list2)
{
List *result = NULL;
List_node *list1_move = NULL;
List_node *list2_move = NULL;
if(list1 == NULL || list2 == NULL){
return result;
}
result = init_list(); //结果链表得初始化
list1_move = list1->head;
list2_move = list2->head;
//如果两个链表都没有遍历完,进行比较
while(list1_move != NULL && list2_move != NULL){
if(list1_move->data <= list2_move->data){
push_back(result, list1_move->data);
list1_move = list1_move->next;
}else{
push_back(result, list2_move->data);
list2_move = list2_move->next;
}
}
//当两个链表中任何一个遍历结束,则把另外一个进行尾部添加
while(list2_move != NULL){
push_back(result, list2_move->data);
list2_move = list2_move->next;
}
while(list1_move != NULL){
push_back(result, list1_move->data);
list1_move = list1_move->next;
}
return result;
}
//11//合并两个有序链表(递归)
//
//l1 node1->node2->node3
//
// l->node1->node4...
//
//l2 node4->node5->node6
//
List *merge_two_lists_recure(List *list1, List *list2)
{
List *node = NULL;
List *l = init_list();
List *l1 = list1;
List *l2 = list2;
if(l1->count == 0)
{
return list2;
}
if(l2->count == 0)
{
return list1;
}
//若l1当前head满足较小情况,将l指向l1,让l1指向l1的下一个节点,进入递归比较l1->head->next形成的链表与l2再进行比较,返回的node链表是后面节点的新链表;
if(l1->head->data < l2->head->data)
{
l->head = l1->head;
l1->head = l1->head->next;
l1->count --;
node = merge_two_lists_recure(l1, l2);
}
else
{
l->head = l2->head;
l2->head = l2->head->next;
l2->count--;
node = merge_two_lists_recure(l1, l2);
}
//完成递归后,l指向node组成的新链表;
l->head->next = node->head;
l->count = node->count +1;
return l;
}
//12 逆置一个链表
//因为会改变结构,为了不失去对后面的节点的信息,使用了三个指针,交换前两个,不丢失第三个 。
//
// | head | tail | count |
// \ \ n-> n - 1
// \ \
// node1->node2->node3->node4
// | | |
// p q m
//
// node1<-node2 node3->node4
// | | | |
// p q m |
// p q m
//
List *reverse_list(List *list)
{
List_node *p_node = NULL;
List_node *q_node = NULL;
List_node *m_node = NULL;
if(list == NULL || list->count < TWO){
return list;
}
//两个节点
if(list->count == TWO){
list->tail->next = list->head;
list->head->next = NULL;
}else{ //三个节点
p_node = list->head;
q_node = p_node->next;
m_node = q_node->next;
p_node->next = NULL;
do{
q_node->next = p_node; //让中间指针的next指向前一个
p_node = q_node; //指针整体向后搬移
q_node = m_node;
m_node = m_node->next;
}while(m_node != NULL);
q_node->next = p_node;
}
//交换头尾指针
swap(&(list->head), &(list->tail), sizeof(List_node *));
return list;
}
//13 链表的拷贝
//使用了尾插法依次将元素插入
List *list_dump(List *list)
{
List * new_list = init_list();
List_node * node = list->head;
for(node = list->head; node; node = node->next)
{
push_back(new_list, node->data);
}
return new_list;
}
//14 判断链表是否有交点
//若两个链表有交点,则尾节点一定是同一个。
//
// l1-> => => =>
// \
// \
// / => => => => => =>
// l2-> => => =>
//
Boolean is_list_intersect(List *list1, List *list2)
{
if(list1 == NULL || list2 == NULL){
return FALSE;
}
//
if( list1->tail == list2->tail)
{
printf("intersect\n");
return TRUE;
}
else
{
printf("nothing\n");
return FALSE;
}
}
//15 得到第一个交点
// l1 =>
// \
// / => => => => => =>
// l2 => => => =>
// 长度不等,先获得两个链表都离相遇点长度相等的地方开始依次比较。
List_node *get_first_common_node(List *list1, List *list2)
{
int list1_len = 0;
int list2_len = 0;
int distance = 0;
List_node *p_node = NULL;
List_node *q_node = NULL;
if(!is_list_intersect(list1, list2)){ //判断两个链表是否有交点
return NULL;
}
list1_len = list1->count;
list2_len = list2->count;
p_node = list1->head;
q_node = list2->head;
//判断较长链表并首先进行移动
if(list1_len >= list2_len){
distance = list1_len - list2_len;
while(distance--){
p_node = p_node->next;
}
}else{
distance = list2_len - list1_len;
while(distance--){
q_node = q_node->next;
}
}
//依次对对应节点进行判断是否相等,如果相等则是第一个相交节点
while(p_node != q_node){
p_node = p_node->next;
q_node = q_node->next;
}
return p_node;
}
//16 判断一个链表是否有环
//
// - - - 、
// \ - - - - 、
// \ / |
// \ /
// ` - - - -
//
Boolean has_circle(List *list)
{
List_node *p = list->head; //slow
List_node *q = list->head; //fast
while(p != NULL && q != NULL && q->next !=NULL)
{
p = p->next; //一次一步
q = q->next->next; //一次两步
//如果有环,则一定会相遇
if(p == q)
{
printf("we found circle!\n");
return TRUE;
}
}
printf("no circle!\n");
return FALSE;
}
//17 找到带环链表的环入口节点
//
// - - - 、
// \ - - - - 、
// \ / |
// \ /
// 入口点 ` - -*- -
// \
// 相遇点
// |<-x->|
// |<---a--->|<-----r---->|
// |<----------L--------->|
// 我们设整个链表长L,入口点与相遇点长x,起点到入口点长度为a;
// 快指针走的长度是慢指针的2倍,由于快指针可能不止走了一圈;
// 慢指针走了s步,即快指针走了2s步。
//
// ∵ 2s = s + nr ;
// ∴ s = nr;
//
// a + x = nr
// a + x = nr = (n-1)r + r = (n-1)r + L -a
// a = (n-1)r + (L -a -x)
// 链表从头到环入口点长度 = (n -1)环长 + 相遇点到环入口长度
// 所以, 从链表头和相遇点各设一个一步长的指针,必定会在相遇点回合。
//
List_node *find_circle_first_node(List *list)
{
List_node * entry = NULL;
if(!has_circle(list))
{
return NULL;
}
List_node *p = list->head; //slow
List_node *q = list->head; //fast
while(p != NULL && q != NULL && q->next !=NULL)
{
p = p->next;
q = q->next->next;
if(p == q)
{
break; //相遇点p和q
}
}
//p为相遇点开始
q = list->head; //q从链表头开始
while(q != p)
{
p =p->next;
q =q->next;
}
printf("we found the node is %d\n", q->data);
return q;
}
四、测试功能
测试代码:
#include "list.h"
int main()
{
int i = 0;
int num = 0;
List *list = NULL;
List *list1 = NULL;
List *list2 = NULL;
List_node * node = NULL;
printf("1.init_list:\n");
list = init_list();
list1 = init_list();
list2 = init_list();
printf("3.1 list1 push_front :\n");
for(i = 0; i < 6; i++)
{
push_front(list1, rand()%100);
}
show_list(list1);
printf("3.2 list2 push_back:\n");
for(i = 0; i < 4; i++)
{
push_back(list2, rand()%100);
}
show_list(list2);
printf("4.1 list1 pop_front:\n");
pop_front(list1);
show_list(list1);
printf("4.2 list2 pop_back:\n");
pop_back(list2);
show_list(list2);
printf("7.1 sort_list1_ascend:\n");
sort_list_ascend(list1);
show_list(list1);
printf("7.2 sort_list2_descend:\n");
sort_list_descend(list2);
show_list(list2);
printf("12 reverse_list2:\n");
reverse_list(list2);
show_list(list2);
#if 0
printf("10. list : merge_two_lists(list1, list2):\n");
list = merge_two_lists(list1, list2);
show_list(list);
#endif
#if 1
printf("11. list : merge_two_lists_recure(list1, list2):\n");
list = merge_two_lists_recure(list1, list2);
show_list(list);
#endif
printf("8.1 show_list(list);\n");
show_list(list);
printf("8.2 reverse_show_list(list);\n");
reverse_show_list(list);
#if 1
printf("find_node:\n");
printf("which one?(value to find)\n");
scanf("%d",&num);
printf("found num of list is :%d \n",find_node(list, num)->data);
#endif
#if 1
printf("find_revise_node:\n");
printf("which one index ?(reverse)\n");
scanf("%d",&num);
printf("reverse num of list is :%d \n",find_revise_node(list, num)->data);
#endif
printf("5.3 find_mid_node(list):\n") ;
printf("%d \n",find_mid_node(list)->data);
printf("9. get_list_count:\n");
printf("the count of list is :%d\n", get_list_count(list));
#if 1
printf("6.3 delete_one_node:");
printf("which one?(to delete)\n");
scanf("%d",&num);
node = create_node();
node = find_node(list,num);
delete_one_node(list, node);
show_list(list);
#endif
#if 1
printf("6.1 modify_node :\n");
modify_node(list, 1, 26);
show_list(list);
#endif
#if 1
printf("6.2 insert_node: \n");
insert_node(list, 2, 18);
show_list(list);
#endif
#if 1
printf("16. has_circle:\n");
//list->head->next->next->next->next->next = list->head->next;
if(has_circle(list))
{
printf("17 find_circle_first_node:\n");
find_circle_first_node(list);
}
#endif
printf("13. list1:list_dump(list)\n");
list1 = list_dump(list);
printf("list1: ");
show_list(list1);
printf("14. is_list_intersect:\n");
if(is_list_intersect(list1, list))
{
printf("the common node: %d\n",get_first_common_node(list1, list));
}
printf("2. destroy_list:\n");
destroy_list(&list1);
destroy_list(&list2);
destroy_list(&list);
return 0;
}
运行结果:
root@aemonair:/ctl_list# ./bin/list
1.init_list:
3.1 list1 push_front :
35 93 15 77 86 83
3.2 list2 push_back:
86 92 49 21
4.1 list1 pop_front:
93 15 77 86 83
4.2 list2 pop_back:
86 92 49
7.1 sort_list1_ascend:
15 77 83 86 93
7.2 sort_list2_descend:
92 86 49
12 reverse_list2:
49 86 92
11. list : merge_two_lists_recure(list1, list2):
15 49 77 83 86 86 92 93
8.1 show_list(list);
15 49 77 83 86 86 92 93
8.2 reverse_show_list(list);
93 92 86 86 83 77 49 15
find_node:
which one?(value to find)
92
found the value: 92
found num of list is :92
find_revise_node:
which one index ?(reverse)
2
reverse num of list is :92
5.3 find_mid_node(list):
86
9. get_list_count:
the count of list is :8
6.3 delete_one_node:which one?(to delete)
92
found the value: 92
15 49 77 83 86 86 93
6.1 modify_node :
15 26 77 83 86 86 93
6.2 insert_node:
15 26 18 77 83 86 86 93
16. has_circle:
no circle!
13. list1:list_dump(list)
list1: 15 26 18 77 83 86 86 93
14. is_list_intersect:
nothing
2. destroy_list:
总结
至此。我们完成了对带控制信息链表的定义,实现,测试;
我们会发现加上控制信息之后,一些操作被封装起来,有可能更加便捷。
这是一种思路,一种方法。
以上解法也不一定是最简便快捷的,只是其中一种实现而已。
我们还将继续对数据结构进行进一步的探讨。