• 快排、归并排序(分治)、堆排序


    一、高速排序

    1)算法简单介绍

    高速排序是由C. A. R. Hoare所发展的一种排序算法。

    其基本思想是基本思想是,通过一趟排序将待排记录分隔成独立的两部分。当中一部分记录的keyword均比还有一部分的keyword小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。

    2)算法描写叙述

    高速排序使用分治法来把一个串(list)分为两个子串行(sub-lists)。
    步骤为:
    1、从数列中挑出一个元素。称为 "基准"(pivot),
    2、又一次排序数列,全部元素比基准值小的摆放在基准前面,全部元素比基准值大的摆在基准的后面(同样的数能够到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
    3、递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。


    最差时间复杂度:O(n^2)
    最优时间复杂度:O(n log n)
    平均时间复杂度:O(n log n)
    最差空间复杂度:依据实现的方式不同而不同

    3)算法代码

    高速排序有非常多版本号,但关键是划分的思想。

    void QuickSort(int L[], int l, int r)
    {
    	int i, j, pivot;
    
    	if(l >= r)
    		return;
    	//std::swap(L[l], L[(l+r)/2]);//以中间的数作为基准数的
    	//int p = l + rand() % (r - l + 1);	  //rand_partition()
    	//std::swap(L[l], L[p]);
    	i = l, j=r, pivot = L[l];
    	while(i < j)
    	{
    		while(i < j && L[j] >= pivot)//从右向左找第一个比key小的数
    			j--;
    		if(i < j)
    			L[i++] = L[j];
    
    		while(i < j && L[i] <= pivot)//从左向右找第一个比key大的数
    			i++;
    		if(i < j)
    			L[j--] = L[i];
    	}
    	L[i] = pivot;
    	QuickSort(L, l, i-1);
    	QuickSort(L, i+1, r);
    }
    //编程珠玑:p112
    //最坏:当元素所有同样时,每次划分变为1:n-1,O(n^2)
    //qsort1与qsort2全然等价,仅仅只是qsort1以第一个元素为主元;qsort2以最后一个元素为主元
    void qsort1(int L[], int l, int r)
    {
    	if (l >= r)
    		return;
    	int i = l, j;
    	for (j = l + 1; j <= r; j++)	  
    		/* invariant: L[l+1..i] < L[l] && L[i+1..j-1] >= L[l]*/
    		if (L[j] < L[l])	 //<
    			std::swap(L[++i], L[j]);
    	std::swap(L[l], L[i]);
    	/* L[l..i-1] < L[i] <= L[i+1..r]*/
    	qsort1(L, l, i - 1);
    	qsort1(L, i + 1, r);
    }

    //算法导论:p94
    //最坏:当元素所有同样时。每次划分变为n-1:1,O(n^2)
    void qsort2(int L[], int l, int r)
    {
    	if (l >= r)
    		return;
    	 int pivot = L[r];
    	 int i = l - 1, j;
    	 for (j = l; j <= r - 1; j++)
    		 if (L[j] <= pivot)	//L[r]
    			 std::swap(L[++i], L[j]);
    	 std::swap(L[++i], L[r]);  //和主元交换
    	 qsort1(L, l, i - 1);
    	 qsort1(L, i + 1, r);
    }

    //编程珠玑:p114
    //双向扫描。当数组元素全同样时。由qsort1/2中的O(n^2)变为nlogn
    void qsort3(int L[], int l, int r)
    {
    	if (l >= r)
    		return;
    	int pivot = L[l];
    	int i = l, j = r + 1;
    	while(i <= j)
    	{
    		do 
    		{
    			i++;
    		} while (i <= r && L[i] < pivot);
    		do 
    		{
    			j--;
    		} while (L[j] > pivot);
    		if (i > j)
    			break;
    		std::swap(L[i], L[j]);
    	}
    	//print(L, l, r);putchar('
    ');
    	std::swap(L[l], L[j]);
    	qsort3(L, l, j - 1);
    	qsort3(L, j + 1, r);
    }

    这个版本号的代码有错误,临时没有发现原因??~

    //算法导论:p103
    //C.A.R.hoare版	结果不正确,代码有误?
    void qsort4(int L[], int l, int r)
    {
    	if (l >= r)
    		return;
    	int pivot = L[l];
    	int i = l - 1, j = r + 1;
    	while (true)
    	{			
    		do 
    		{
    			j--;
    		} while (L[j] > pivot);
    		do 
    		{
    			i++;
    		} while (L[i] < pivot);
    
    		if (i <= j)
    			std::swap(L[i], L[j]);   
    		else
    			break;
    	}
    	//std::swap(L[l], L[j]);
    	qsort3(L, l, j - 1);
    	qsort3(L, j + 1, r);
    }
      高速排序的非递归版本号:
    //       <=pivot  |    ?     | >=pivot
    //                i          j
    static int Partition(int L[], int l, int r)
    {
    	int i, j, pivot;
    	//std::swap(L[l], L[(l+r)/2]);//以中间的数作为基准数的
    	//int p = l + rand() % (r - l + 1);	  //rand_partition()
    	//std::swap(L[l], L[p]);
    	i = l, j=r, pivot = L[l];
    	while(i < j)
    	{
    		while(i < j && L[j] >= pivot)//从右向左找第一个比key小的数
    			j--;
    		if(i < j)
    			L[i++] = L[j];
    
    		while(i < j && L[i] <= pivot)//从左向右找第一个比key大的数
    			i++;
    		if(i < j)
    			L[j--] = L[i];
    	}
    	L[i] = pivot;
    	return i;
    }
    
    
    void qsort_non_recursive(int L[], int n)
    {	
    	stack<int>st;
    	int l = 0, r = n - 1, mid;
    	if (l >= r)
    		return;
    
    	mid = Partition(L, l, r);
    	if(l < mid - 1)
    	{
    		st.push(l), st.push(mid - 1);
    	}
    	if(r > mid + 1)
    	{
    		st.push(mid + 1), st.push(r);
    	}
    
    	 //用栈保存每一个待排序子串的首尾元素下标,下一次循环时取出这个范围,对这段子序列进行partition操作  
    	while(!st.empty())
    	{
    		r = st.top(); st.pop();
    		l = st.top(); st.pop();
    
    		mid = Partition(L, l, r);
    		if(l < mid - 1)
    		{
    			st.push(l), st.push(mid - 1);
    		}
    		if(r > mid + 1)
    		{
    			st.push(mid + 1), st.push(r);
    		}
    	}
    }

    (1)、寻找数组第i大的数

    (算法导论p120,若元素互异,则存在期望线性时间)

    //一次划分后,主元左边的数都小于主元,主元右边的数都大于或者等于主元
    int Rand_Partition(int A[], int l, int r)
    {  
        int p = l + rand()% (r - l + 1);
        std::swap(A[l], A[p]);
        
        return Partition(A, l, r);
    }
    
    

    int Rand_Select(int A[], int l, int r, int i) //ith big in A[l, ..., r]
    {
        int pivotloc, k;
        if(l == r)
            return A[l];
            
        pivotloc = Rand_Partition(A, l, r);
        k = pivotloc - l + 1;
        
        if(i == k)
            return A[pivotloc];
        else if(i < k)
            return Rand_Select(A, l, pivotloc-1, i);
        else return Rand_Select(A, pivotloc+1, r, i-k);
    }
    上面是基于迭代。以下是递归方式:
    int Rand_Select(int A[], int l, int r, int i) //ith big in A[l, ..., r]
    {
    	int pivotloc, k;
    
    	if(l == r) 
    		return A[l]; 
    
    	pivotloc = Rand_Partition(A, l, r); 
    	k = pivotloc - l + 1; 
    
    	if(i == k) 
    		return A[pivotloc]; 
    	else if(i < k) 
    		return Rand_Select(A, l, pivotloc-1, i); 
    	else 
    		return Rand_Select(A, pivotloc+1, r, i-k);
    }

    (2)最小的前k个数
    能够建立含有k个元素的大顶堆求解。也能够利用划分的思想,代码例如以下:
    void GetleastNumber(int input[], int n, int output[], int k)
    {
    	if (n <= 0 || k <= 0 || k > n)
    		return;
    
    	int l = 0, r = n - 1;
    	int pivotloc = Partition(input, l, r);
    
    	//l..|....|.....|...r
    	//	 p < k-1 <= p
    
    	while (pivotloc != k - 1)
    	{
    		if (pivotloc < k - 1)
    		   pivotloc = Partition(input, pivotloc + 1, r);
    		else
    		   pivotloc = Partition(input, l, pivotloc - 1);
    	}
    	memcpy(output, input, k * sizeof(int));
    }


    二、归并排序

    1)算法简单介绍

            归并排序是建立在归并操作上的一种有效的排序算法。该算法是採用分治法(Divide and Conquer)的一个很典型的应用。归并排序是一种稳定的排序方法。

           将已有序的子序列合并。得到全然有序的序列;即先使每一个子序列有序。再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。

    2)算法描写叙述

        归并排序详细算法描写叙述例如以下(递归版本号):

        1、Divide: 把长度为n的输入序列分成两个长度为n/2的子序列。

        2、Conquer: 对这两个子序列分别採用归并排序。

        3、Combine: 将两个排序好的子序列合并成一个终于的排序序列。

        归并排序的效率是比較高的。设数列长为N将数列分开成小数列一共要logN步,每步都是一个合并有序数列的过程,时间复杂度能够记为O(N)。故一共为O(N*logN)。由于归并排序每次都是在相邻的数据中进行操作,所以归并排序在O(N*logN)的几种排序方法(高速排序,归并排序。希尔排序,堆排序)也是效率比較高的。


    3)算法代码

    归排序代码的写法能够有集中,以下分别给出:
    (1)首先分配好一个暂时数组空间,避免归并函数内频繁申请内存
    //将两个有序的数组a[first,...,mid], a[mid+1,...,last]合并成一个有序的(借助temp数组)
    void MergeTwoSortedArray(int a[], int first, int mid, int last, int temp[])
    {
    	int i = first, j = mid + 1;
    	int k = 0;
    	while ( i <= mid && j <= last)
    		if (a[i] < a[j])
    			temp[k++] = a[i++];
    		else
    			temp[k++] = a[j++];
    
    	while (i <= mid)
    		temp[k++]  = a[i++];
    	while (j <= last)
    		temp[k++] = a[j++];
    
    	//for (i = 0; i < k; i++)
    	//	a[first + i] = temp[i];
    	memcpy(a + first, temp, sizeof(int) * (last - first + 1));
    }
     
    //递归地对a[first,...,last]区间的元素二分排序再合并
    void MergeSortR(int a[], int first, int last, int temp[])
    {
    	if (first < last)
    	{
    		int mid = (first + last) / 2;	     //将a[first, last]平分为a[first,...,mid]和a[mid+1,...,last]
    		MergeSortR(a, first, mid, temp);     //左边有序:递归地将a[first,...,mid]归并为有序的temp[first,...,mid]
    		MergeSortR(a, mid + 1, last, temp);	 //右边有序:递归地将a[mid+1,...,last]归并为有序的temp[mid+1,...,last]
    		MergeTwoSortedArray(a, first, mid, last, temp);	//合并后所有有序:将有序的temp[first,...,mid]和有序的temp[mid+1,...,last]归并到a[first, last]
    	}
    }
    
    void MergeSort(int a[], int n)
    {
    	int *temp = new int[n];	  //暂时数组
    	if (temp == NULL) return;
    	memset(temp, 0, sizeof(int)*n);
    	MergeSortR(a, 0, n - 1, temp);
    	delete []temp;
    }
    (2)假设每次在归并函数内部分配暂时的数组空间,归并函数的写法也能够有几种:
    //先将左右有序序列复制到暂时数组后再直接归并到原数组
    void MergeTwoSortedArray1(int a[], int first, int mid, int last)
    {
    	int i, j;
    	int n1 = mid - first + 1;
    	int n2 = last - mid;
    	int *L = new int[n1];	//暂时数组
    	int *R = new int[n2];
    
    	//for (i = 0; i < n1; i++)   //左边有序序列
    	//	L[i] = a[first + i];
    	//for (j = 0; j < n2; j++)
    	//	R[j] = a[mid + 1 + j];	//右边有序序列
    
    	for (i = first; i <= mid; i++)
    		L[i - first] = a[i];
    	for (j = mid + 1; j <= last; j++)
    		R[j - mid - 1] = a[j];
    
    	i = 0, j = 0;
    	int k = first;
    	while (i < n1 && j < n2)
    	{
    		if (L[i] < R[j])
    			a[k++] = L[i++];
    		else
    			a[k++] = R[j++];
    	}
    	while (i < n1)
    		a[k++] = L[i++];
    	while (j < n2)
    		a[k++] = R[j++];
    
    	delete []L;
    	delete []R;
    }

    //先将左右有序序列复制到暂时数组后再直接归并到原数组(使用哨兵)
    void MergeTwoSortedArray2(int a[], int first, int mid, int last)
    {
    	int i, j;
    	int n1 = mid - first + 1;
    	int n2 = last - mid;
    	int *L = new int[n1 + 1];	//暂时数组,末尾为哨兵元素
    	int *R = new int[n2 + 1];
    
    	//for (i = 0; i < n1; i++)   //左边有序序列
    	//	L[i] = a[first + i];
    	//for (j = 0; j < n2; j++)
    	//	R[j] = a[mid + 1 + j];	//右边有序序列
    	//L[i] = INT_MAX, R[j] = INT_MAX;	 //末尾加入哨兵元素
    
    	for (i = first; i <= mid; i++)
    		L[i - first] = a[i];
    	for (j = mid + 1; j <= last; j++)
    		R[j - mid - 1] = a[j];
    	L[i - first] = INT_MAX, R[j - mid - 1] = INT_MAX;
    
    	i = 0, j = 0;
    	for (int k = first; k <= last; k++)
    	{
    		if (L[i] < R[j])
    			a[k] = L[i++];
    		else
    			a[k] = R[j++];
    	}
    
    	delete []L;
    	delete []R;
    }

    void MergeSortR1(int a[], int first, int last)
    {
    	if (first < last)
    	{
    		int mid = (first + last) / 2;
    		MergeSortR1(a, first, mid);
    		MergeSortR1(a, mid + 1, last);
    		MergeTwoSortedArray3(a, first, mid, last);
    	}
    }
    
    void MergeSort1(int a[], int n)
    {
    	MergeSortR1(a, 0, n - 1);
    }

    4)求逆序对

    利用归并排序的思想能够实现求逆序数对

    int Merge(int a[], int first, int mid, int last, int temp[])
    {
    	int inversion = 0;
    	int i = first, j = mid + 1, k = 0;
    	while (i <= mid && j <= last)
    	{
    		if (a[i] <= a[j])
    		{
    			temp[k++] = a[i++];
    		}else	 //a[i] > a[j],而a[first,..,i,..,mid]递增有序,因此a[i~mid]与a[j]构成逆序对,共mid-i+1个
    		{
    			temp[k++] = a[j++];
    			inversion += mid - i + 1;
    		}
    	}
    	while (i <= mid)
    		temp[k++] = a[i++];
    	while (j <= last)
    		temp[k++] = a[j++];
    	memcpy(a + first, temp, sizeof(int) * (last - first + 1));
    	return inversion;
    }
    
    int MergeInversionR(int a[], int first, int last, int temp[])
    {
    	int inversion = 0;
    	if (first < last)
    	{
    		int mid = (first + last) >> 1;
    		inversion += MergeInversionR(a, first, mid, temp);	//找左半段的逆序对数目
    		inversion += MergeInversionR(a, mid + 1, last, temp);//找右半段的逆序对数目
    		inversion += Merge(a, first, mid, last, temp);		//在找完左右半段逆序对以后两段数组有序,然后找两段之间的逆序对。最小的逆序段仅仅有一个元素。  
    	}
    	return inversion;
    }
    
    int MergeInversion(int a[], int n)
    {
    	int inversion = 0;
    	int *temp = new int[n];
    	memset(temp, 0, sizeof(int)*n);
    	inversion = MergeInversionR(a, 0, n - 1, temp);
    	delete []temp;
    	return inversion;
    }

    三、堆排序

    利用堆实现堆排序&优先队列



  • 相关阅读:
    wpf之ComboBox绑定
    初始WPF
    WinForm 中 comboBox控件之数据绑定
    C# 操作注册表
    VS创建Web项目的两种形式WebSite和WebApplicationd的区别!
    网页加载慢的问题及部分解决办法
    获取CPU序列号
    53种使网页增速的方法、工具和资源
    Server Application Error报错解决方案
    20个使Web开发更高效的工具列表
  • 原文地址:https://www.cnblogs.com/tlnshuju/p/7205983.html
Copyright © 2020-2023  润新知