排序算法们

 

1. 快速排序

算法描述

快速排序使用分治法来把一个串(list)分为两个子串(sub-lists)。具体算法描述如下:

1)从数列中挑出一个元素,称为 “基准”(pivot);
2)重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
3)递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
 

20201027实现:en,跟n年前相比写的更清晰了,多多定义变量!

#pragma once
#include <iostream>

class Sort
{
public:
	Sort();
	~Sort();

	void bubbleSort(int arr[], int N) {
		if (arr == nullptr)
			return;
		if (N < 2)
			return;

		for (int j = 0; j < N; j++) {
			bool swaped = false;
			for (int i = 0; i + 1+j < N; i++) {
				if (arr[i] > arr[i + 1]) {
					int temp = arr[i];
					arr[i] = arr[i + 1];
					arr[i + 1] = temp;
					swaped = true;
				}
			}
			if (!swaped)
				return;
		}
	}

	void quickSort(int arr[], int N) {
		if (arr == nullptr)
			return;
		if (N < 2)
			return;

		quickSortSovler(arr, 0, N-1);
	}

	//en,跟n年前相比写的更清晰了,多多定义变量!
	void quickSortSovler(int arr[], int stIdx, int endIdx) {
		if (stIdx >= endIdx)
			return;

		int pivotVal = arr[stIdx];
		//50 10 60 70 3 200  - > 3 10 50 60 70 200
		//50 30 1 70 
		//keep pivot still. iterate array from piovt's index+1;
		//还是双指针(快慢)的思想!
		int leftPartEndIdx = stIdx; //all the elements of left part is less than pivotVal;
		int iteIdx = stIdx + 1;
		for (; iteIdx < endIdx; iteIdx++) {
			if (arr[iteIdx] < pivotVal) {
				leftPartEndIdx++;
				swap(arr,iteIdx,leftPartEndIdx);
			}
		}
		swap(arr, stIdx, leftPartEndIdx);
		quickSortSovler(arr,0, leftPartEndIdx);
		quickSortSovler(arr, leftPartEndIdx+1, endIdx);
	}
	void swap(int arr[], int idx1,int idx2) {
		if (idx1 != idx2)
		{
			int tmp = arr[idx1];
			arr[idx1] = arr[idx2];
			arr[idx2] = tmp;
		}
	}
	void test1() {
		int array[4] = {20,30,90,100};
		bubbleSort(array, 4);
		for (int i = 0; i < 4; i++) {
			std::cout << array[i]<<"  ";
		}
		std::cout << std::endl;

	}
	void quickSortTest1() {
		const int N = 6;
		int array[N] = { 20,3,90,10,-9,2 };
		bubbleSort(array, N);
		for (int i = 0; i < N; i++) {
			std::cout << array[i] << "  ";
		}
		std::cout << std::endl;

	}
};
int main() {
	Sort obj;
	obj.test1();
	obj.quickSortTest1();

	return 0;
}

C语言实现: 自己写的

//A待排序数组, len:数组长度
void QuickSort(int A[], int len)
{
    if(len <= 1)
        return;
    int pivotIdx = 0; //选择最左侧的元素作为基准
    int nextPutIdx = pivotIdx+1; //如果发现有比pivot小的元素,则直接与A[nextPutIdx]交换
    for(int idx = nextPutIdx; idx < len; idx++)
    {
        if(A[idx] < A[pivotIdx])
        {
            swap(A,idx,nextPutIdx);
            //交换之后,更新nextPutIdx,保证A[pivotIdx+1],A[pivotIdx+2],...,A[nextPutIdx-1]都比A[pivot]小.
            nextPutIdx++;  
        }
    }
    
    //下面两行使得A[pivotIdx]左侧的都比A[pivotIdx]小,右侧的都比A[pivotIdx]大。
    swap(A,pivotIdx, nextPutIdx-1);
    pivotIdx = nextPutIdx-1;

    QuickSort(A,pivotIdx);      //继续递归左半部分
    QuickSort(&A[pivotIdx+1],len-pivotIdx-1);//继续递归右半部分
}

注意,上面实现的细节,面试时被问到过一次如果实现一次递归,把比pivot小的都移到左边,忘了,结果死在这个地方。

思想是快慢指针!! 知道了思想就不会忘记了!

表述: 以最左侧的元素为基准,定义两个指针slow, fast,slow指针指向基准后面的第一个元素,fast指针指向基准后面的第二个元素,然后,从fast指针位置开始遍历数组,如果fast元素比pivot大或相等,则不动作,只是fast++,,如果fast元素比pivot小,则将两者指向的元素互换,并且fast++,slow++。   

遍历完成后,将pivot值(整个遍历过程中pivot没有参与)与slow元素互换,over!

 

快速排序最差的情况对应的时间复杂度:

--之前被问到,没反应过来,死了。

以最左端的pivot为基准完成一次后,所有的元素都在pivot左边,即所有元素都比pivot小,然后,对pivot左边的这部分进行动作时,又是同样的情况,则时间为n+(n-1)/2*2 +(n-2)/2*2 + ... +1,所以o(n^2)

快速排序最好的情况对应的时间复杂度:

已经排好序了,然后,每次对相应的部分进行时,虽然没有元素的交换,只有比较,但是也要遍历一遍,古为o(n).

 

   工程实践中,关注的是一个算法的最坏、最好时间复杂度,而不是“平均”, 在工程时间中关注的是稳定性,所以常用堆排序。    在工程中o(n^2)的时间复杂度是不能容忍的!  -- 天翼工程师

 

2 归并排序

算法描述

归并排序是建立在归并操作上的一种有效的排序算法。 该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。

步骤:

  • 把长度为n的输入序列分成两个长度为n/2的子序列;
  • 对这两个子序列分别采用归并排序;
  • 将两个排序好的子序列合并成一个最终的排序序列。

     注意:递归的终止条件是待组合的两个子序列的长度都为1.
 

归并排序在超大文件排序中的应用: https://blog.csdn.net/qq_35865125/article/details/106309726



自己写的代码:

//实现归并排序,分治,递归
void MergeSort(int a[], int stIdx, int endIdx)
{
    int midIdx = (endIdx + stIdx)/2;//I ever :(endIdx - stIdx)/2!!
    if(endIdx - stIdx > 1)
    {
        MergeSort(a, stIdx, midIdx);
        MergeSort(a, midIdx+1, endIdx);

        merge(a,stIdx,midIdx,endIdx);//Do not forget it. I ever made mistake
    }
    else if(endIdx - stIdx == 1)
    {
        merge(a,stIdx,midIdx,endIdx);
        return;
    }
    else if(endIdx == stIdx)
    {
        return;
    }
}


//A[stIdx], ... ,A[midIdx]已经完成从小到大的排序
//A[midIdx+1], ..., A[endIdx]已经完成从小到大的排序
//该函数将这两段合并,从小到大的顺序
void merge(int A[],int stIdx, int midIdx,int endIdx)
{
    int totalLen = endIdx - stIdx + 1;
    int* array = (int*)malloc(sizeof(int)*totalLen );
    int arrayIdx = 0;

    int idx1 = stIdx; int idx2 = midIdx+1;
    while((idx1 <= midIdx)||(idx2 <= endIdx))//
    {
        if((idx1 <= midIdx)&&(idx2 <= endIdx))
        {
            if(A[idx1] <= A[idx2])
            {
                array[arrayIdx] = A[idx1];
                idx1++;
            }
            else
            {
                array[arrayIdx] = A[idx2];
                idx2++;
            }
            arrayIdx++;
        }
        else if((idx1 <= midIdx))
        {
            for(int i = idx1; i <= midIdx;i++ )
            {
                array[arrayIdx] = A[i];
                arrayIdx++;
            }
            break;
        }
        else if(idx2 <= endIdx)
        {
            for(int i = idx2; i <= endIdx;i++ )
            {
                array[arrayIdx] = A[i];
                arrayIdx++;
            }
             break;
        }

    }

    for(int idx = stIdx;idx<=endIdx;idx++)
    {
        A[idx] = array[idx-stIdx];
    }
    delete array;
}

 

归并排序的时间复杂度:O(n*log(n))

https://blog.csdn.net/qq_32534441/article/details/95098059

T(n)=2*T(n/2) + n;.....

Note:
归并排序是一个稳定的排序,平均时间复杂度,最好,最坏都是n*lgN, 因为总要先拆分成最小单位的一小对一小对,再merge。


2020.11练习,写得挺好:

归并排序写的挺好自己
void mergeTwoSubArray(vector<int>& vec, const int leftPartStIdx, const int leftPartEndIdx,
    const int rightPartStIdx, const int rightPartEndIdx) {
    if ((leftPartEndIdx + 1) != rightPartStIdx) {
        cout << "error case!" << endl;
        return;
    }
    const int totalNum = rightPartEndIdx - leftPartStIdx + 1;
    vector<int> sortedResult;
    int leftPartIdx = leftPartStIdx;
    int rightPartIdx = rightPartStIdx;

    while (leftPartIdx <= leftPartEndIdx || rightPartIdx <= rightPartEndIdx) {
        if ((leftPartIdx <= leftPartEndIdx && rightPartIdx <= rightPartEndIdx)) {
            int currLeftPartVal = vec[leftPartIdx];
            int currRightPartVal = vec[rightPartIdx];

            if (currLeftPartVal <= currRightPartVal) {
                sortedResult.push_back(currLeftPartVal);
                leftPartIdx++;
            }
            else {
                sortedResult.push_back(currRightPartVal);
                rightPartIdx++;
            }
        }
        else if (leftPartIdx <= leftPartEndIdx) {
            sortedResult.push_back(vec[leftPartIdx]);
            leftPartIdx++;
        }
        else if (rightPartIdx <= rightPartEndIdx) {
            sortedResult.push_back(vec[rightPartIdx]);
            rightPartIdx++;
        }
    }
    for (int i = 0; i < totalNum; i++) {
        vec[leftPartStIdx + i] = sortedResult[i];//Note:leftPartStIdx + i !!
    }
}


void mergeSort(vector<int>& vec, const int stIdx, const int endIdx) {

    if (stIdx >= endIdx)
        return;
    int midIdx = (stIdx + endIdx) / 2;
    mergeSort(vec, stIdx, midIdx);
    mergeSort(vec, midIdx+1, endIdx);
    mergeTwoSubArray(vec,stIdx, midIdx, midIdx+1, endIdx);
}


int main() {
    vector<int> vec1{3,65,69,   3,5,10,10,29};

    //mergeTwoSubArray(vec1, 0, 2, 3,7);//先测试一下该函数的正确性
    mergeSort(vec1, 0, vec1.size()-1);
    return 0;
}

 



3.冒泡排序
 

好久没有写,以为一分钟就写出来,没想到还是想了一会儿,岁月让我忘记了它/

//排成从小到大的顺序
void BubbleSort(int a[], int len)
{
    for(int idx1 = 0;idx1 < len; idx1++)
    {
        int flag = 1;
        for(int idx2 = 1; idx2 < len; idx2++)//遍历一遍后,最大的数会沉到最底
        {
            if(a[idx2] < a[idx2 -1])
            {
                swap(a, idx2,idx2-1);
            }
            flag = 0;
        }
        if(flag)
            break;//如果遍历时,一次交换也没有发生,则说明已经排好了
    }
}

 

4.堆排序

选择排序的一种,  可利用数组的特点快速定位指定索引的元素。

堆的概念:

堆分为两种:最大堆最小堆,两者的差别在于节点的排序方式。

在最大堆中,父节点的值比每一个子节点的值都要大。在最小堆中,父节点的值比每一个子节点的值都要小。这就是所谓的“堆属性”,并且这个属性对堆中的每一个节点都成立。

 

上图是一个最大堆,每一个父节点的值都比其子节点要大。1072 都大,751都大。

最大堆总是将其中的最大值存放在树的根节点,  最小堆的根节点元素总是树中的最小值。从而可以快速的访问到“最重要”的元素。


堆排序讲解:

https://www.cnblogs.com/chengxiao/p/6129630.html

https://www.jianshu.com/p/0d383d294a80

https://blog.csdn.net/weixin_42250655/article/details/82878317

 

 


时间复杂度:

堆排序在最坏情况下,其时间复杂度也为O(nlogn)

 

由构建初始堆+交换堆顶元素和末尾元素并重建堆两部分组成。其中构建初始堆经推导复杂度为O(n),在交换并重建堆的过程中,需交换n-1次,而重建堆的过程中,根据完全二叉树的性质,[log2(n-1),log2(n-2)...1]逐步递减,近似为nlogn。所以堆排序时间复杂度一般认为就是O(nlogn)级。

 


Mycode:


#include<iostream>

class CHeapSort {


public:
	//nodeIdx对应的元素为子树的根节点, 除了根节点之外,该子树的其他元素符合最大堆定义。
//通过该函数将这棵子树重新调整为符合最大堆定义的子树,即将根节点放在合适位置。
	void adjusttHeap(int array[], const int len, const int nodeIdx)
	{
		int fatherIdx = nodeIdx;
		int lChildIdx = 2 * fatherIdx + 1; //左孩子的索引
		int rChildIdx = lChildIdx + 1;  //右孩子的索引
		int biggerChildIdx = lChildIdx; //指示哪个孩子更大

		while (fatherIdx * 2 + 1 < len)//一直到叶子节点
		{
			if (lChildIdx < len - 1 && array[lChildIdx] < array[rChildIdx])//右孩子更大
				biggerChildIdx = rChildIdx;
			if (biggerChildIdx > len - 1)
				break;

			if (array[fatherIdx] < array[biggerChildIdx]) // 如果父节点比孩子小. 最终array数组的后端是小值
			{

				//array[fatherIdx] = array[biggerChildIdx];
				swap(array, fatherIdx, biggerChildIdx);
			}
			else{
				//array[fatherIdx] = array[nodeIdx];
				break;
				
			}

			fatherIdx = biggerChildIdx;
			lChildIdx = 2 * fatherIdx + 1; //左孩子的索引
			rChildIdx = lChildIdx + 1;  //右孩子的索引
		}

		
	}
	void swap(int aray[], int i, int j)
	{
		int tmp = aray[i]; aray[i] = aray[j];
		aray[j] = tmp;

	}
	void heapSort(int array[], int len)
	{
		//初始化堆
		//从树的最下方的非叶子节点开始调整
		//array[fatherNodeIdx*2+1]对应fatherNodeIdx的左子树
		for (int fatherNodeIdx = len / 2; fatherNodeIdx >= 0; fatherNodeIdx--)//array[0]对应的是整个树的根节点呀
		{
			adjusttHeap(array, len, fatherNodeIdx);
		}

		//排序,不断地将堆顶部元素移动到array的最后,即,将堆顶元素与树的最下层的最右侧的叶子交换
		//然后,调整数
		for (int lastIdx = len - 1; lastIdx >= 0; lastIdx--)
		{
			//交换
			int temp = array[0];
			array[0] = array[lastIdx];
			array[lastIdx] = temp;

			int unsortedLen = lastIdx;
			adjusttHeap(array, unsortedLen, 0);
		}
		//Now,array 中存储着有序的序列。
	}

};


这个testCase有误:

CHeapSort objHeapSort;
    //int a[4] = { 1, 3, 2,-9 };
    //int n = 4;
    int a[10] = { 1, 3, 2, 9 ,6 ,8, 7, 5, 0, 4 };
    int n = 10;
    printf("原数组为: \n");
    for (int i = 0; i < n; i++)
        printf("%d    ", a[i]);

    printf("\n");

    objHeapSort.heapSort(a, n);

    printf("排序后的数组为:\n ");
    for (int i = 0; i < n; i++)
        printf("%d\t", a[i]);
    printf("\n");

 

 

 

 


Ref:

https://www.cnblogs.com/onepixel/articles/7674659.html

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

First Snowflakes

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值