笔面的高频问题。又是现场没答出来,回来再马后炮:
用栈实现:
1。每次把支点的右段入栈(当然只记录该段的起始与结束标记);
2。然后继续对支点的左段重复过程1,若左段的元素小于2个,则不需要再重复1,转到3;
3。左段已排好,从栈中取出最新的右段,转到1,若栈空则结束。
代码如下:
// HelloWorld.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <stack>
static int CNT = 0; //记录分段函数执行的次数
int Div(int* a,int s,int e) //找到支点的位置,这和递归写法是一样的
{
CNT++;
if(s >= e)
return 0;
int temp = a[s];
while( s < e )
{
while( s < e && a[e] >= temp )
--e;
a[s] = a[e];
while( s < e && a[s] <= temp )
++s;
a[e] = a[s];
}
a[s] = temp;
return s;
}
void QS(int* a,int s,int e)
{
stack<int> sta,end; //sta记录右段的起始,end记录右段的结束
while(1)
{
int k = Div(a,s,e);
if(k<e-1)//当节点位置右边至少有两个元素时才将右段入栈
{
sta.push(k+1);
end.push(e);
}
e = k-1; //新的结束位置,准备下一循环
if(k <= s+1)//左半只有一个或零个元素时,不需要再其排序
{
if( sta.empty() )
return;
//取出栈中的段,继续排
s = sta.top();
sta.pop();
e = end.top();
end.pop();
}
}
}
int _tmain(int argc,char* argv[])
{
const int N = 100;
int a[N];
for(int i=0;i<N;++i)
a[i] = rand()%1000;
QS(a,0,N-1);
show(a,N); //自己加一个显示数组的函数即可,这里不提供
cout<<"CNT="<<CNT<<endl;
return 0;
}
上述讲了非递归算法。下面顺便上一个基于单链表的递归快排(非原创),变化主要发生在分割支点的函数上,为了方便,设一头一尾指针标记链表的开始与结尾,其实过程如图:
从蓝色结点开始,以它为支点,向右走,通过改变链表指针的next,分别建立左分支与右分支,最后再将左右与支点串起来;接着再递归完成左支与右支:
对左支,新的头,尾指针分别是上次的头与支点;
对右支,新的头,尾指针分别是支点与上次的尾。
代码如下:
typedef struct _LN
{
int val;
_LN* pNext;
}LN;
void showLN(LN* head)
{
while(head)
{
cout<<head->val<<",";
head = head->pNext;
}
cout<<endl;
}
LN* DIV(LN* head,LN* rear)
{
int leftcut = 0;
LN * left = NULL, *leftHead = NULL,*right = NULL, *rightHead = NULL, *cur = head->pNext->pNext;
LN * pivot = head->pNext ; //支点的值
while( cur != rear )
{
if( cur->val < pivot->val )
{
++leftcut;
if(left)
{
left->pNext = cur;
left = cur;
}
else
{
left = cur;
leftHead = cur;
}
}
else
{
if(right)
{
right->pNext = cur;
right = cur;
}
else
{
right = cur;
rightHead = cur;
}
}
cur = cur->pNext;
}
if( leftHead )
{
head->pNext = leftHead;
left->pNext = pivot;
}
else
head->pNext = pivot;
if( rightHead )
{
pivot->pNext = rightHead;
right->pNext = rear;
}
else
pivot->pNext = rear;
return pivot;
}
void QS(LN* head,LN* rear)
{
//head 和 rear之间要至少有两个元素才要排序
if( !head || !rear || head->pNext == rear || !head->pNext->pNext || head->pNext->pNext == rear)
return;
LN* pivot = DIV(head,rear);
QS(head,pivot);
QS(pivot,rear);
}
int _tmain(int argc,char* argv[])
{
LN* rear = new LN;
rear->val = -1;
rear->pNext = NULL;
LN* head = new LN;
head->val = -1;
head->pNext = rear;
const int N = 100;
//int arr[N] = {4,2,1,7,6,3,8,4,0,7};
LN a[N];
for (int i=0;i<N;++i)
{
a[i].val = rand()%100;//arr[i];
if(i>0)
a[i-1].pNext = a+i;
}
head->pNext = a;
a[N-1].pNext = rear;
QS(head,rear);
showLN(head);
return 0;
}
单链表归并排序
归并排序的辅助空间O(1),对于单链表,归并排序的实现比快排要简单,以下是代码:
typedef struct _Node
{
int data;
_Node *next;
}Node;
//将已各自排好序,并由head1,head2为头指针的链表合并,返回新的头指针
Node* merge(Node* head1,Node* head2)
{
//新的头结点必是在两个head中选一,新的尾结点也必是两条链中选一
//这里需要注意处理值相等的情况
Node* newhead = (head1->data <= head2->data) ? head1 : head2;
while(head1!=NULL && head2!=NULL)
{
Node *pre1=NULL,*pre2=NULL;
//将链表1中,小于等于head2的部分接过去(只需要最后一个结点的next改写即可)。
//注意这里处理“等于”的情况要与newhead对“等于”的处理相匹配
while(head1!=NULL && head1->data <= head2->data)
{
pre1 = head1;
head1 = head1->next;
}
if (pre1) pre1->next = head2;
if(head1==NULL) break;
while( head2!=NULL && head2->data < head1->data)
{
pre2 = head2;
head2 = head2->next;
}
if (pre2) pre2->next = head1;
}
return newhead;
}
Node* msort(Node *head, int n)
{
if (n<=1)
{
head->next=NULL; //打断将要排序的每小段,保证每一小段总是以NULL为结束标志
return head;
}
Node *mid=head;
int len1 = n/2,len2=n-len1;
for (int i=0;i<len1;++i) mid = mid->next;
Node *head1 = msort(head,len1);
Node *head2 = msort(mid,len2);
return merge(head1,head2);
}