归并排序
是建立在归并操作上的一种有效的排序算法,主要可以分为两步,分别是分割与合并,以下将以排序链表(交换节点)来说明。
图片来源:百度百科
1. 合并
合并是归并算法的重点,所以只要解决了合并的问题那归并的算法就比较简单了,所以这里先解决合并问题,从上图中可以看出整个合并的过程中,就是将两个有序链表合并为一个有序链表。
//链表节点的结构体
typedef struct node
{
int val;
struct node *next;
}Node;
//合并两个有序链表为一个有序链表
//参数1:有序链表1
//参数2:有序链表2
//返回值:合并后的有序链表
Node *merge_list(Node *list1,Node *list2)
{
Node *head = NULL; //合并后的有序链表的头指针
Node *min_node = NULL; //用于临时储存两条有序链表中
//当链表1与链表2都为空时跳出循环
while(list1 != NULL || list2 != NULL)
{
if(list1 != NULL)
{
//当list1不为空时执行这个部分
if(list2 != NULL)
{
//当list1与list2都不为空时执行这个部分
min_node = select_min(&list1,&list2);//通过select_min()函数找到两条链表中最小的头结点,并将其移除原来的链表,并将节点返回出来
min_node -> next = NULL;//由于以下是用尾插法,所以需要将这个节点的指针域初始化为NULL
if(head == NULL)
{
//尾插法,需要判断新链表是否有节点,如果没有,直接将新链表的头指针指向节点
head = min_node;
}
else
{
//如果链表不为空则执行以下代码
Node *tail = find_tail(head);//通过find_tail()找到新链表的尾结点
tail -> next = min_node;//将取下的节点加入到链表尾
}
}
else
{
//当list2为空时执行这个部分代码
//当list2为空时,不再需要对比两条链表,只需要把list1整条加入到新链表的尾部,并跳出循环
Node *tail = find_tail(head);//通过find_tail()函数找到新链表的尾结点
tail -> next = list1;//将list1完整加入到新链表中
break;
}
}
else
{
//当list1为空时执行这个部分代码
//当list1为空时,不再需要对比两条链表,只需要把list2整条加入到新链表的尾部,并跳出循环
Node *tail = find_tail(head);//通过find_tail()函数找到新链表的尾结点
tail -> next = list2;//将list2完整加入到新链表中
break;
}
}
return head;//返回两条有序链表合并后的新链表
}
//找到两条链表中最小的节点并将其取下,并返回
//参数1:用于比对的链表1,由于有可能会改变其链表的头结点,所以需要传入其头指针的地址,所以为二级指针
//参数2:用于比对的链表2,由于有可能会改变其链表的头结点,所以需要传入其头指针的地址,所以为二级指针
//返回值:返回被取下的最小节点的地址
Node* select_min(Node **l1,Node **l2)
{
Node *temp = NULL;
if((*l1) -> val > (*l2) -> val)
{
temp = *l2;
*l2 = (*l2) -> next;
return temp;
}
else
{
temp = *l1;
*l1 = (*l1) -> next;
return temp;
}
}
//用于找到链表的尾结点
//参数:需要找尾结点的链表的头指针
//返回值:传入链表的尾结点的地址
Node *find_tail(Node *head)
{
//遍历链表,当节点的指针域为NULL时,说明当前节点为尾结点,结束循环
while(head -> next != NULL)
{
head = head -> next;
}
return head;
}
3. 分割
归并的分割是二等分,也就是说分割点为长度的一半:
l
e
n
/
2
len /2
len/2
在链表中因为需要将找到分割点的前一个节点,将其指针域赋值为NULL确保前半段为一条完整的链表,在用另一个头指针记录分割点来形成两条链表。在这个分割的过程中,需要将一开始的链表分割带不能在分割,也就是一个节点单独称为一条链表,所以需要在代码中用到递归。所以可以设计一下函数,先分割,再将其合并合并为一条有序链表,这个过程中也就完成了归并排序:
//归并排序
//参数1:需要分割并合并的链表的头指针
//返回值:合并后的链表的头节点的地址
Node *sort_list(Node *head)
{
int len = list_len(head);//使用list_len()函数求链表的长度
//如果传入链表的长度为1,则不能再分割,作为递归的出口
if(len == 1)
{
return head;
}
Node *temp = head;//申请另一个头指针用于储存分割点的前一个节点
//len为链表长度,所以分割点为len/2,所以通过循环找到分割点的前一个节点,则循环len/2-1次
//循环结束后temp将指向分割点的前一个节点
for(int i = 0;i < ((len / 2) - 1);i++)
{
temp = temp -> next;
}
Node *head2 = temp -> next;//申请一个节点用于储存分割后的第二条链表,head2指向分割点
temp -> next = NULL;//将前后链表完全分离,将分割点的前一个节点的指针域设为空,称为尾结点
head = sort_list(head);//将链表1递归分割,并返回为一个有序链表
head2 = sort_list(head2);//将链表2递归分割,并返回为一个有序链表
return merge_list(head,head2);//将返回来的两条有序链表合并为一条有序链表并返回其头结点地址
}
//计算链表的长度
//参数1:需要求链表长度的链表的头结点地址
//返回值:所传入链表的长度
int list_len(Node *head)
{
int len = 0;
//遍历链表,没有一个节点len加一
while(head != NULL)
{
len++;
head = head -> next;
}
return len;
}
在完成两个步骤(执行sort_list()函数)之后,之前的无序链表将排序为一条有序链表。