快排,非递归;单链表快排;单链表归并

笔面的高频问题。又是现场没答出来,回来再马后炮:

用栈实现:

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);
}






  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值