练习《算法导论》之排序:插入排序,归并排序,堆排序,快速排序

本文详细介绍并对比了插入排序、归并排序、堆排序和快速排序等经典排序算法,提供了C++实现代码,适用于初学者入门及进阶学习。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


           最近开始学习《算法导论(第3版)》,非常经典的厚书,从排序开始,本文目前总结了插入排序,归并排序,堆排序和快速排序,所有排序给出了用C++实现的测试代码,直接保存为Cpp文件即可运行,分享给大家,方便学习。


         一.插入排序 (InsertionSort)              


        《算法导论》中给出的例子是许多人抓拍时排序手中扑克牌。开始左手为空且桌面上的牌面向下,每次拿到一张牌要在手中从右向左(从大到小)与手中每张牌比较。拿在手中的牌总是排序好的,原来这些牌是桌子上牌堆顶部的牌。
        插入排序是最简单的排序,很容易理解,时间复杂度最高,是O(n^2)。
 C++实现如下:
#include <iostream>
using namespace std;

int main() {
      int N;
      cin>>N;
      if(N) {
              int *a = new int[N];
              for(int i = 0; i < N; i++ ) {
                      int cur;
                      cin>>cur;
                      int k;
                      for(k = i-1; k >= 0 && a[k] > cur; k--) {
                                    a[k+1] = a[k];
                      }
                      a[k+1] = cur;
              }
              cout<<a[0];
              for(int i = 1; i < N; i++) {
                     cout<<' '<<a[i];
              }
      }
      return 0;
}


    二. 归并排序(MergeSort)                

分治法 (Divde and Conquer)

           分治法(Divide and Conquer)的概念在递归的求解子问题时提出。分治模式在每层递归时都有三个步骤:

           分解 + 解决 + 合并

归并排序

化为合并两堆牌面朝上,已排序且最小牌在上的两堆扑克牌问题。

MERGE-SORT(A, p, r)

if(p < r){
		int q = (p+r)/2;
		MERGE_SORT(A,p,q);                                                                                                                       
		MERGE_SORT(A,q+1,r);
		MERGE(A,p,q,r);
	}

关键是合并两个已排序的序列,通过写一个辅助函数MERGE(A, p, q, r) 来完成这个合并功能。

  1. 将A划分为L[] 和R[]两个数组,分别自然排序;
  2. 在堆底(即最大值后面)设置哨兵牌∞
  3. 从p到r分别取两堆中较小牌到A[]中

归并排序的时间复杂度为O(n*lgn)。

#include <vector>
using std::vector;
#include <iostream>
using namespace std;

void MERGE(vector <int> *A, int p, int q, int r){
	//initialize Left and Right Array, which are both already sorted
	vector <int> Left;
	vector <int> Right;
	Left.resize(q-p+1);
	Right.resize(r-q);
	for(int i=0; i<Left.size() ;i++)		
		Left.at(i) = A->at(p + i);   //A is not started from 0
	for(int j=0; j<Right.size() ;j++)	
		Right.at(j) = A->at(q+1 + j);
	Left.push_back(100);
	Right.push_back(100);                                                   

	//compare each Array to choose smaller one to A
	int j=p;
	int i_left = 0, i_right = 0;
	while( j <= r ){
		if(Left.at(i_left) <= Right.at(i_right))
			A->at(j++) = Left.at(i_left++);                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       
		else
			A->at(j++) = Right.at(i_right++);
		
	}
}


void MERGE_SORT(vector <int> *A, int p, int r)		//no return
{
	if(p==r)	
		{//A_out.at(p)=A.at(p); 
	}
	else if(p<r)
	{
		int q = (p+r)/2;
		MERGE_SORT(A,p,q);                                                                                                                                                                        
		MERGE_SORT(A,q+1,r);
		MERGE(A,p,q,r);
	}
	
}

void main()
{
	int Array[]={9,3,6,1,4,1,12};
	vector <int> A;
	for(int i=0; i<7; i++ )		A.push_back(Array[i]);
	
	MERGE_SORT(&A,0,6);
	//result = MERGE(A,0,2,6);
	for(int i=0; i<A.size();i++)
		cout<<" "<<A.at(i);
	system("pause");
}


归并排序应用:查找数组中的逆序对

int merge(int a[], int temp[], const int l, const int r, const int end) {  //合并两个已排序好的序列
	int i = l;
	int left = l;
	int right = r;
	int cnt = 0;
	while(left<r && right <= end) {
		while(a[left] <= a[right] && left < r) {
			temp[i++] = a[left++];
		}
		while(a[left]>a[right] && right <= end) {
			temp[i++] = a[right++];
			cnt += r - left;
		}
	}
	if( right > end ) {
		while( i <= end )		
			temp[i++] = a[left++];
	}
	else if( left >= r ) {
		while( i <= end )	temp[i++] = a[right++];
	}
	//***************************
	for(int j = l; j <= end; ++j )
	{
		a[j] = temp[j];
	}
	return cnt;
}

int MergeSortIter(int a[], int temp[], int start, int end) {
	if(start==end) {
		temp[start] = a[start];
		return 0;
	}
	else if(start<end) {
		int len = (end - start)/2;
		int left = MergeSortIter(a, temp, start, start+len);
		int right = MergeSortIter(a, temp, start+len+1, end);
		int cur = merge(a, temp, start, start+len+1, end);
		return left+right+cur;
	}
}
int InversePairs(int a[], int n) {
	int res = 0;
	if(n<=1)	return res;
	int * temp = new int[n];
	return MergeSortIter(a, temp, 0, n-1);
	//swap(a, temp);

}


                  三. 堆排序(HeapSort)                                                 

简介

堆排序集合了插入排序和归并排序两种算法的优点:

  1. 与归并一样,时间复杂度是O(nlgn);
  2. 与插入一样,有空间原址性,只需要常数个额外的元素空间存储临时数据。
二叉堆是可以看成一个完全二叉树。

最大堆

      除了根节点以外的所有节点都要满足A[parent(i)] >= A[i] 。我们可以在线性时间内把一个无序数组构造成一个最大堆。下面程序中,build_max_heap() 完成这一过程。

#include <iostream>
#include <vector>
using std::vector;
using namespace std;

class MyHeap{
private:
	int heapsize;
	vector<int> heap;
public:
	MyHeap();
	~MyHeap();
	void build_max_heap();
	void max_heapify(int i);
        void heapsort();
};

MyHeap::MyHeap(){
	heap.push_back(100);
	int Array[7]={9,3,6,1,4,1,12}; 
	heapsize = sizeof(Array)/sizeof(int);
	for(int i=0; i<heapsize; i++)
	{
		heap.push_back(Array[i]);
	}
}
MyHeap::~MyHeap(){
}

/*bottoms up, max heap 最大堆
e.g.			  12
			    /    \
			  4       9
		    /  \    /  \
          1     3  1    6
*/
void MyHeap::build_max_heap(){
	for(int i= heapsize/2; i>0; i--)
	{
		max_heapify(i);
	}
}
//Suppose left_child(i) and right_child(i) are both max heaps.
// This function keeps heap i also a max heap.
void MyHeap::max_heapify(int i){
	  int largest = 0;
	  int left = 2*i;
	  int right = 2*i + 1 ;
	  //compare the three data: index, left and right.
	  //and put the smallest right; the largest root.
	  if( left <= heapsize && heap[i] < heap[left] )
		  largest = left;
	  else
		  largest = i;
	  if( right <= heapsize && heap[largest] < heap[right] )
		  largest = right;

	  if( largest != i )
	  {
		  int temp = heap[largest];
		  heap[largest] = heap[i];
		  heap[i] = temp;

		  max_heapify(largest);
	  }
	  
}
void main(){
	  MyHeap data;
	  data.build_max_heap();
          //data.heapsort();
}

堆排序

  1.  先把堆化成最大堆;                                                                 时间复杂度O(n)
  2. 因为最大元素总在根节点处,把它与A[n]互换;
  3. 从堆中去掉节点n(通过减少A.heapsize),剩余节点中原来根的孩子节点仍然是最大堆,新的根节点则不一定;
  4. 所以需要调用max_heapify()来维护其最大堆性质;                n-1次调用O(lgn)
  5. 重复2~4步骤,直到全部排好。
void MyHeap::heapsort(){
	build_max_heap();
	for(int i = heap.size()-1; i>1 ; i--)
	{
		int temp = heap.at(1);
		heap.at(1) = heap.at(i);
		heap.at(i) = temp;

		this->heapsize--;
		max_heapify(1);
	}
}


           四. 快速排序(QuickSort)                                 

       快速排序也采用了分治思想,算法的关键是Partition过程,它使选定的主元(pivot element)左边都比它小,右边都比它大, 并返回排好后主元的位置。

具体实现      

  全部代码如下:

#include <iostream>
using namespace std;

int Partition(int A[], int start, int end)
{
	int pivot = A[start]; //tempvalue pivot
	int i= start, j= end ;
	while(i<j)    
	{
		while(A[j] >= pivot && i<j )    //notice , this is not if, scan all over
			j--;			//only if the pivot is less then the right element, pointer--
		A[i] = A[j] ;        //element moves every time
		
		while( A[i] <= pivot && i<j )
			i++;
		A[j] = A[i];        //直到 >pivot了,停止,把这个补到后面的空位
	}
	A[i] = pivot;
	return i;
}
void QuickSort(int A[], int i, int j)
{
	if(i<j)
	{
		int pivot = Partition(A, i, j);
		QuickSort(A, i, pivot - 1);
		QuickSort(A, pivot + 1, j);
	}
}
void main()
{
	 int A[] = {0, 23, 3, 5,46, 23, 20,88};     //instead of store middle value in A[0], we use a tempvalue
	 int Length = sizeof(A)/sizeof(int);
	 QuickSort(A, 0, Length-1);
}

Partition说明

      注意在Partition内部,将选定的轴值存为中间变量(tempvalue pivot),先从右向左扫描,如果一直比轴值大,指针就一直左移,这对于本来已经大体有序的序列,就避免了大幅改动,如本例中第一个循环的A[0]= 0; 一旦不满足了就把空位填上,因为这里恰好使用初始位置的值作为轴值,所以刚好空位处在较小位置;最终的效果将是空位在上未排好的部分左右来回移动,直到全部拍好。所以这里只能用while而不是if。

下图可以作为中间示意图,浅灰色为确定小于pivot已排好部分,深灰色为确定大于pivot已排好的部分,白色为尚未确定的部分,直到白色只剩一个时停止,将pivot值填入白色空位。


五. 冒泡排序(BubbleSort)    

冒泡排序每排一次最大的泡沉到底,已排序部分无需再排。

冒泡和快排都是交换排序,无需申请新的空间。

快排和归并都是递归的,复杂度nlogn。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值