排序算法总结(c++版本)

文章目录

      • 冒泡排序
      • 插入排序
      • 选择排序
      • 希尔排序
      • 归并排序
      • 快速排序
      • 堆排序

冒泡排序

它的原理很简单,每次从左到右两两比较,把大的交换到后面,每次可以确保将前M个元素的最大值移动到最右边;

步骤:

  1. 从左开始比较相邻的两个元素x和y,如果 x > y 就交换两者
  2. 执行比较和交换,直到到达数组的最后一个元素
  3. 重复执行1和2,直到执行n次,也就是n个最大元素都排到了最后
void bubble_sort(vector<int> &nums)
{
    for (int i = 0; i < nums.size() - 1; i++) { // times
        for (int j = 0; j < nums.size() - i - 1; j++) { // position
            if (nums[j] > nums[j + 1]) {
                int temp = nums[j];
                nums[j] = nums[j + 1];
                nums[j + 1] = temp;
            }
        }
    }
}

改进:
可以提前结束排序 —— 若无交换

void bubble_sort(vector<int> &nums)
{
    for (int i = 0; i < nums.size() - 1; i++) { // times
    	bool isEnd = true;
        for (int j = 0; j < nums.size() - i - 1; j++) { // position
            if (nums[j] > nums[j + 1]) {
                int temp = nums[j];
                nums[j] = nums[j + 1];
                nums[j + 1] = temp;
                isEnd = false;
            }
        }
        if(isEnd)
        	break; // 若未进行交换,则代表已经完成排序;故而可以停止循环了;
    }
}

时间复杂度:
由于两重循环,故而为 O ( n 2 ) O(n^2) O(n2); 最好情况: O ( n ) O(n) O(n)
空间复杂度: O ( n ) O(n) O(n)
稳定性:稳定

插入排序

插入排序的原理是从左到右,把选出的一个数和前面的数进行比较,找到最适合它的位置放入,使前面部分有序;

步骤:

  1. 从左开始,选出当前位置的数x
  2. 和它之前的数y比较,找到其应该插入的位置
  3. 选择有序部分后一个数字,插入到前面有序部分,直到没有数字可选择
void InsertSort(int &num, int len){  // len: 长度
	for(int i=1;i<len-1;++i)
	{
		int j = i-1;
		int temp = num[i];
		while(num[i] < num[j] && j>= 0)
			{
				num[j+1] = num[j];
				j--;
			}
		if(j<0)
			num[0] = temp;
		else
			num[j+1] = temp;
	}
}	

时间复杂度: O ( n 2 ) O(n^2) O(n2)
稳定性: 稳定

选择排序

选择排序的原理是,每次都从乱序数组中找到最大(最小)值,放到当前乱序数组头部,最终使数组有序。

void selection_sort(vector<int> &nums)
{
    for (int i = 0; i < nums.size(); i++) { // position
        int min = i;
        for (int j = i + 1; j < nums.size(); j++) {
            if (nums[j] < nums[min]) {
                min = j;
            }
        }

        int temp = nums[i];
        nums[i] = nums[min];
        nums[min] = temp;
    }
}

时间复杂度: O ( n 2 ) O(n^2) O(n2)
空间复杂度: O ( n ) O(n) O(n)
稳定性:不稳定

希尔排序

希尔排序从名字上看不出来特点,因为它是以发明者命名的。它的另一个名字是“递减增量排序算法“。这个算法可以看作是插入排序的优化版,因为插入排序需要一位一位比较,然后放置到正确位置。为了提升比较的跨度,希尔排序将数组按照一定步长分成几个子数组进行排序,通过逐渐减短步长来完成最终排序

为什么这个算法能起作用:
开始的时候gap较大,子序列中的数据较少,所以最开始的时候算法运行较快,随着算法进行,gap 逐渐变小,子序列中元素的个数也就越来越多,所以排序工作可能会变慢,但是由于前面已经完成了部分排序工作,因而在很大程度上减轻了后来的工作量,于是最终总体的排序速度还是比较快的

步骤:

  1. 待排序数据的长度 l e n len len,第一轮排序取步长 g a p = l e n / 2 gap = len/2 gap=len/2
  2. 根据步长进行分组,并排序;
  3. 下一轮,取 g a p = g a p / 2 gap = gap/2 gap=gap/2,并分组排序;
  4. 重复第三步,直到 g a p = = 1 gap == 1 gap==1

实例:
排序算法总结(c++版本)_第1张图片
第一步: l e n = 6 , g a p = 3 len = 6, gap = 3 len=6,gap=3
排序算法总结(c++版本)_第2张图片
第二轮: g a p = 1 gap = 1 gap=1

代码实现:

void shellSort(int a[], int n)  //a -- 待排序的数组, n -- 数组的长度
{
    int i,j,gap;   // gap为步长,每次减为原来的一半。
    for (gap = n / 2; gap > 0; gap /= 2)
    {
        // 共gap个组,对每一组都执行直接插入排序
        for (i = 0 ;i < gap; i++)
        {
            for (j = i + gap; j < n; j += gap) 
            { 
                // 如果a[j] < a[j-gap],则寻找a[j]位置,并将后面数据的位置都后移。
                if (a[j] < a[j - gap])
                {
                    int tmp = a[j];
                    int k = j - gap;
                    while (k >= 0 && a[k] > tmp)
                    {
                        a[k + gap] = a[k];
                        k -= gap;
                    }
                    a[k + gap] = tmp;
                }
            }
        }
    }
}

时间复杂度:与步长相关
空间复杂度: O ( n ) O(n) O(n)
稳定性:不稳定

归并排序

归并排序是采用分治法(Divide and Conquer)的一个典型例子。这个排序的特点是把一个数组打散成小数组,然后再把小数组拼凑再排序,直到最终数组有序

void merge_array(vector<int> &nums, int low, int mid, int high, vector<int> &temp)
{
    int lb = low, rb = mid, tb = low;
    while (lb != mid && rb != high)
        if (nums[lb] < nums[rb])
            temp[tb++] = nums[lb++];
        else
            temp[tb++] = nums[rb++];

    while (lb < mid)
        temp[tb++] = nums[lb++];
    
    while (rb < high)
        temp[tb++] = nums[rb++];

    for (int i = low;i < high; i++)
        nums[i] = temp[i];
}

void merge_sort(vector<int> &nums, int low, int high, vector<int> &temp)
{
    int mid = (low + high) / 2;
    if (mid != b) {
        merge_sort(nums, low, mid, temp);
        merge_sort(nums, mid+1, high, temp);
        merge_array(nums, low, mid, high, temp);
    }
}

这个实现中加了一个temp,是和原数组一样大的一个空间,用来临时存放排序后的子数组的

时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn) —— 这个实现中加了一个temp,是和原数组一样大的一个空间,用来临时存放排序后的子数组的

空间复杂度: O ( n ) O(n) O(n)

稳定性:不稳定

快速排序

快速排序也是利用分治法实现的一个排序算法。快速排序和归并排序不同,它不是一半一半的分子数组,而是选择一个基准数,把比这个数小的挪到左边,把比这个数大的移到右边。然后不断对左右两部分也执行相同步骤,直到整个数组有序;

void quick_sort(vector<int> &nums, int low, int high)
{
    if (low < high) {
        int lb = low, hb = high;
        while (lb < hb) {
            while (nums[hb] >= nums[low] && lb < hb)
                hb--;
            while (nums[lb] <= nums[low] && lb < hb)
                lb++;
            swap(nums[lb], nums[hb]);
        }
        swap(nums[low], nums[lb]);
        quick_sort(nums, low, lb);
        quick_sort(nums, lb + 1, high);
    }
}

时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)
空间复杂度: O ( n ) O(n) O(n)
稳定性:不稳定

堆排序

堆排序经常用于求一个数组中最大k个元素时。因为堆实际上是一个完全二叉树,所以用它可以用一维数组来表示。因为最大堆的第一位总为当前堆中最大值,所以每次将最大值移除后,调整堆即可获得下一个最大值,通过一遍一遍执行这个过程就可以得到前k大元素,或者使堆有序

在了解算法之前,首先了解在一维数组中节点的下标:

  1. i节点的父节点 parent(i) = floor((i-1)/2)
  2. i节点的左子节点 left(i) = 2i + 1
  3. i节点的右子节点 right(i) = 2i + 2

步骤:

  1. 构造最大堆(Build Max Heap):首先将当前元素放入最大堆下一个位置,然后将此元素依次和它的父节点比较,如果大于父节点就和父节点交换,直到比较到根节点。重复执行到最后一个元素。
  2. 最大堆调整(Max Heapify):调整最大堆即将根节点移除后(取出)重新整理堆。整理方法为将根节点和最后一个节点交换,然后把堆看做n-1长度,将当前根节点逐步移动到其应该在的位置。
  3. 堆排序(HeapSort):重复执行2,直到所有根节点都已移除
void heap_sort(vector<int> &nums)
{
    int n = nums.size();
    for (int i = n / 2 - 1; i >= 0; i--) { // build max heap
        max_heapify(nums, i, nums.size() - 1);
    }
    
    for (int i = n - 1; i > 0; i--) { // heap sort
        int temp = nums[i];
        num[i] = nums[0];
        num[0] = temp;
        max_heapify(nums, 0, i);
    }
}

void max_heapify(vector<int> &nums, int beg, int end)
{
    int curr = beg;
    int child = curr * 2 + 1;
    while (child < end) {
        if (child + 1 < end && nums[child] < nums[child + 1]) {
            child++;
        }
        if (nums[curr] < nums[child]) {
            int temp = nums[curr];
            nums[curr] = nums[child];
            num[child] = temp;
            curr = child;
            child = 2 * curr + 1;
        } else {
            break;
        }
    }
}

时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)
空间复杂度: O ( n ) O(n) O(n)
稳定性:不稳定

排序算法总结(c++版本)_第3张图片

你可能感兴趣的:(c++)