原文地址:http://blog.sina.com.cn/s/blog_6fb300a30100ng0s.html
常见和链表相关的算法
一、 链表排序
链表排序和数组排序的思路类似,只是链表操作起来比较麻烦,因为不能随机访问,所以只能借助于类似于前置或后置插入,添加等概念来完成。下面给出了链表排序的几种方法。
辅助代码:
//单链表节点的定义
typedef struct LinkNode{
int val;
struct LinkNode* next;
}LinkNode;
//由一个数组创建单链表
LinkNode* CreateList(int A[], int count)
{
if(NULL == A)
return NULL;
LinkNode* head = (LinkNode*)malloc(sizeof(LinkNode));
head->val = A[0];
head->next = NULL;
LinkNode* p = head;
for (int i = 1; i < count; ++i)
{
p->next = (LinkNode*)malloc(sizeof(LinkNode));
p->next->val = A[i];
p->next->next = NULL;
p = p->next;
}
return head;
}
//显示该单链表
void ShowList(LinkNode* L)
{
LinkNode* p = L;
printf("%d", p->val);
while(p->next)
{
p = p->next;
printf("-->%d", p->val);
}
printf("\n");
}
1. 插入排序(以从小到大排序为例)
链表排序最容易想到的是插入排序,它的基本想法是从第一个节点开始,每次将一个节点放到结果链表,并保证每个节点加入到结果链表前后,结果链表都是有序的。每个节点在被链入结果链表时有三种情况:
1)该节点值比结果链表中的所有元素值都大,则将该节点追加到结果链表的最后;
2)该节点值比结果链表中的所有元素值都小,则将该节点插入到结果链表最前面;
3)该节点值在结果链表中处于中间位置,则将该节点插入到合适位置。
下面是该算法思路的流程图:
代码实现如下:
LinkNode* SortLinkListInsertion(LinkNode* head)
{
//链表空或链表只有一个节点,不必排序,直接返回原头结点
if (NULL == head || NULL == head->next)
{
return head;
}
//我们从第二个节点进行处理,第一个节点作为结果链表的初始节点
LinkNode* r = head->next;
LinkNode* tmp;
head->next = NULL; //将结果链表末尾标记为结束
while(NULL != r) //依次处理每一个节点
{
if (r->val < head->val)
{
//将该节点插到结果链表最前,并修改链表头,同时注意辅助变量的使用
tmp = r->next;
r->next = head;
head = r;
r = tmp;
}
else
{
//否则从链表头部开始搜索,注意这里搜索结束的条件是当前被搜索节点不是最后一个节点
LinkNode* p = head;
while(NULL != p->next)
{
//注意只有当节点严格小于被搜索节点的下一节点的值是,才将其插入到被搜索节点后
//这样能保证排序是稳定的!
if (r->val < p->next->val)
{
tmp = r->next;
r->next = p->next;
p->next = r;
r = tmp;
continue; //注意跳出,开始处理下一个节点
}
else
{
p = p->next;
}
}
//此时,p是结果链表中的末尾节点,将被处理节点追加到其后
if (NULL == p->next)
{
tmp = r->next;
r->next = NULL;
p->next = r;
r = tmp;
}
}//end else
}//end while(NULL != r)
return head;
}
2. 选择排序(以从小到大排序为例)
选择排序的基本思想是每次从源链表中选取并移除一个最小的元素,追加到结果链表的尾部,直到源链表变空为止。因此本算法的关键点在于如何从源链表中选取并移除一个最小的元素。考虑到一般情况下,我们在移除链表中的某个元素时,需要知道它的前一个节点的指针(我知道你在想那个trick,但我想我们还是用正统的方法吧),于是我们可以按照最小元素的出现位置分为两种情况:最小元素是链表头部节点和最小元素不是链表头部节点。我们先找出链表中除头结点外的最小值节点,然后再和头节点的值比较,然后进行处理。寻找除头结点外的最小值节点的代码可以很简洁,这也是为什么要分成这两部分处理的原因。
代码实现如下:
LinkNode* SortLinkListSelection2(LinkNode* head)
{
//我们这里即使不进行特殊情况处理,代码也能正常工作,可以代入检查
//if (NULL == head || NULL == head->next)
//{
// return head;
//}
LinkNode* p = NULL; //遍历辅助变量
LinkNode* pminpre = NULL; //指向源链表中除头结点外的最小值节点的前驱节点
LinkNode L = {0, NULL}; //我们这里用了一个哑节点,它能简化后面的代码
LinkNode* Ltail = &L; //Ltail用于指向结果链表的最后一个节点
while (NULL != head && NULL != head->next) //循环处理源链表中节点个数不小于2个的情况
{
pminpre = head;
p = head->next;
while(NULL != p && NULL != p->next) //找出源链表中除头结点外的最小值节点的前驱节点
{
if (p->next->val < pminpre->next->val) //严格小于时才改变pminpre
pminpre = p;
p = p->next;
}
if (head->val <= pminpre->next->val) //和头结点值进行比较处理,值相等时,取头结点
{
Ltail = Ltail->next = head;
head = head->next;
}
else
{
Ltail = Ltail->next = pminpre->next;
pminpre->next = pminpre->next->next;
}
}
Ltail = Ltail->next = head; //最后一个节点直接追加到结果链表的尾部
Ltail->next = NULL; //设置结果链表的结束标记
return L.next;
}
注意上面if语句中的 < 和 <= 判断,他们能使得链表的选择排序是稳定的排序。
3. 冒泡排序(以从小到大排序为例)
链表的冒泡排序不太好想,主要是不太好控制比较边界。这里我们用一个标记指针end表示边界,每完成一趟冒泡后,end会被向前移一下,直到完成N-1趟冒泡。冒泡需要比较并交换相邻的节点,因此我们在实现中使用了pre,cur,n等指针分别表示当前处理节点的前驱,当前处理节点和下一节点。
链表冒泡排序的实现如下:
//asscending sort link list
LinkNode* SortLinkListBubble(LinkNode* head)
{
if (NULL == head)
{
return head;
}
//init end pointer
LinkNode* end = NULL;
while(true)
{
LinkNode* n = head->next;
if (n == end)
break;
//这里单独处理头结点和第二个节点的比较,这是没有哑头节点的链表的一个劣势
if (n->val < head->val)
{
head->next = n->next;
n->next = head;
head = n;
}
LinkNode* pre = head;
LinkNode* cur = head->next;
LinkNode* tmp;
n = cur->next;
while(n != end)
{
if (n->val < cur->val)
{
tmp = n->next;
cur->next = n->next;
n->next = cur;
pre->next = n;
n = tmp;
}
else
{
n = n->next;
}
pre = pre->next;
cur = cur->next;
}
end = cur;
}
return head;
}
4. 快速排序(从小到大排序)
知道链表还可以快速排序是在笔试某公司时才发现的,当时只看到了个QsortList什么的,而且是个填空题,当时没有思路,也没有做出来,只记得那个QsortList带了3个参数,还返回了个链接点指针。回来后细想了一阵,发现原理和数组的快速排序是一样的,只是链表在处理指针时比较麻烦,而且要保证排序后链表还是有序地链接在一起,不能出现掉链等情况,有些绕人。
思路这样,从链表中选取一个节点(一般简单地取头结点),将其值作为pivot,将链表中剩余的节点分成两个子链表,其中一个中的所有值都小于pivot,另一个中的所有值都不小于pivot,然后将这两条链表分别链接到pivot节点的两端。然后对于子链表分别进行递归该过程,直到子链表中只有一个节点为止。
下面给出了链表快速排序的一种实现方法,该实现没有返回参数,链表头也是通过引用方式传入的。
void QsortList(LinkNode*& head, LinkNode* end)
{
if(head == NULL || head == end)
return;
LinkNode* pivot = head;
LinkNode* p = head->next;
LinkNode* head1 = NULL, *tail1 = NULL;
LinkNode* head2 = NULL, *tail2 = NULL;
while(p != end)
{
if(p->val < pivot->val)
{
if(head1 == NULL)
{
head1 = tail1 = p;
}
else
{
tail1->next = p;
tail1 = p;
}
}
else
{
if(head2 == NULL)
{
head2 = tail2 = p;
}
else
{
tail2->next = p;