八大排序算法:思想、图解、C/C++代码

在这里插入图片描述

排序算法保持数字/记录的相对顺序,则该排序算法被称为稳定 ,即,如果您需要排序1 1 2 3,那么如果您不更改前两个1排序的顺序,则算法是稳定的。
键值对进行排序时,这种区别变得更加明显。
假设您需要按照键的升序对以下键值对进行排序:
INPUT: (4,5), (3,2), (4,3),(5,4) ,(6,4)
OUTPUT1:(3,2), (4,5), (4,3),(5,4), (6,4) 稳定
OUTPUT2:(3,2), (4,3), (4,5),(5,4), (6,4) 不稳定(改变了原来的相对顺序)
在这里插入图片描述
对排序算法的选取需要考虑的因素有以下四点:
1.待排序的记录数目n的大小;
2.记录本身数据量的大小,也就是记录中除关键字外的其他信息量的大小;
3.关键字的结构及其分布情况;
4.对排序稳定性的要求。

设待排序元素的个数为n:
1)当n较大,应采用时间复杂度为O(nlogn)的排序方法:快速排序、堆排序、归并排序
2)当n较大,内存空间允许,且要求稳定性 ——归并排序
3)当n较小,采用直接插入或直接选择排序。
5)一般不使用或不直接使用传统的冒泡排序。

[2 7 6 12 4 3 9]

一、两种插入排序:将元素插入到合理位置
1.插入排序(打扑克齐牌)
思想:将第一个元素看做有序序列,把剩下的第二个元素到最后一个元素依次扫描(N-1趟),将扫描到的每个元素插入有序序列的适当位置-----与前一个位置元素比较,如果小于前一个位置元素,则将前一个位置元素右移一位,再与当前位置的前一个元素比较…元素位置与下标顺序对应(如果待插入元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面)

插入排序图解

void insertSort(int arr[],int len)//待排序列及其长度
{
        for(int i = 1; i < len; i++) //共N-1趟,每一趟放置位置i上的元素到正确位置
        {
                int temp = arr[i]; //位置i上的元素
                int j = i-1;      //j初始为i的前一个位置i-1
                while((j >= 0) && (temp < arr[j]))//位置i的元素比i-1位置的元素小
                {
                        arr[j+1] = arr[j];//大的元素右移,temp悬空插到合适位置
                        j--; //可能接着比较i-2位置的元素......
                }
                arr[j+1] = temp;//把位置i上的元素放在正确位置,跳过while则此元素不变
        }
}

在这里插入图片描述

测试用例:

#include "Sort.h"

 void Print(int a[],int len)
{
	for(int i = 0; i < len; ++i)
	{
		cout<<a[i]<<" ";
	}
	cout<<endl;
}

 void test()
 {   //升序排序
	 int a [] = {2,7,6,12,4,3,9};
	 int len = sizeof(a)/sizeof(a[0]);
	 cout<<"before sort :";
	 Print(a,len);

	 InsertSort(a,len);
	 
	 cout<<"after sort :";
	 Print(a,len);
 }
int main ()
{
	test();
	return 0;
}

2.希尔排序(缩小增量排序)
思想:把序列按下标的一定增量分成若干组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个序列恰被分成一组,进行最后一次整体插入排序,算法便终止。我们选择增量gap=length/2,缩小增量继续以gap = gap/2的方式。

希尔排序图解

void ShellSort(int arr[], int len) 
{
      int gap, i, j;
      int temp;
      for (gap = len >> 1; gap > 0; gap >>= 1)//划分若干组
      {
            for (i = gap; i < len; i++)//每组内部是插入排序
            {
                  temp = arr[i];
                  j = i - gap;
                  while( j >= 0 && temp < arr[j])
                  {
                        arr[j + gap] = arr[j];
                        j -= gap;
                  }
                  arr[j + gap] = temp;
            }
      }
}

二、两种选择排序:将最小/最大元素交换放到末尾
3.选择排序
思想:首先在序列中找到最小元素,存放到排序序列的起始位置(第一个位置)-交换
再从剩余未排序元素中继续寻找最小元素,然后放到已排序序列的末尾(第二个位置)。
重复第二步,直到找出所有元素放到排序排列中。

选择排序图解
冒泡、选择、插入图解

void swap(int *a,int *b) //交换两个数
{
    int temp = *a;
    *a = *b;
    *b = temp;
}
void SelectionSort(int arr[], int len)
{
    int i,j;
    for (i = 0 ; i < len - 1 ; i++)
    {
            int min = i;//将第一个元素初始化为最小元素,每一轮min初试值向右移动1个位置
            for (j = i + 1; j < len; j++)   //从第二个元素开始遍历未排序的元素
            {
                  if (arr[j] < arr[min])    //找到剩余未排序序列中的最小值
                        min = j;            //记录当前最小值元素下标
            }
            swap(&arr[min], &arr[i]);    //将最小元素与第二个元素做交换
          //swap(arr[min], arr[i]); //也可以直接使用C++库函数
    }
}

在这里插入图片描述

4.堆排序
思想:堆排序是一种利用堆这种数据结构的概念来排序的选择排序
堆是一种完全二叉树,分为两种方法:
大顶堆:每个节点的值都大于或等于其子节点的值,在堆排序算法中用于升序排列;
小顶堆:每个节点的值都小于或等于其子节点的值,在堆排序算法中用于降序排列;

a.将无需序列(数组)构建成一个堆,根据升序降序需求选择大顶堆或小顶堆;
b.将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端;
c.重新调整结构(构建堆),使其满足堆定义,然后继续交换堆顶元素与当前未排序末尾元素,反复执行调整+交换步骤,直到整个数组序列有序。//构建就是调整

堆排序图解

void HeapSort(int arr[], int len) 
{
    //初始化,i从最后一个父节点(即最后一个非叶节点)开始调整-即构建堆
    for (int i = len / 2 - 1; i >= 0; i--)
        max_heapify(arr, i, len - 1);
    //将堆顶元素和未排序末尾元素(已经排好的元素前一位)交换,将最大值放到末尾
    //再重新调整使得满足堆定义,继续将堆顶元素与未排序末尾元素交换,直到排序完毕
    for (int i = len - 1; i > 0; i--) 
    {
        swap(arr[0], arr[i]);
        max_heapify(arr, 0, i - 1);
    }
}
//构建/调整为堆
void max_heapify(int arr[], int start, int end)//调整的开始节点下标和结束节点下标
{
    // 建立父节点指针和子节点指针
    int dad = start;
    int son = dad * 2 + 1;
    while (son <= end) 
    { // 若两子节点指针在范围内才做比较
        if (son + 1 <= end && arr[son] < arr[son + 1]) 
            son++; //先比较两个子节点大小,选择最大的保存
        if (arr[dad] > arr[son]) 
            return;//如果父节点大于较大子节点则代表调整完毕,直接跳出函数
        else 
        { //否则交换父子内容再继续子节点和孙节点比较
            swap(arr[dad], arr[son]);//C++库函数
            dad = son;
            son = dad * 2 + 1;
        }
    }
}

三、两种交换排序
5.冒泡排序
思想:对相邻的元素进行两两比较,顺序相反(前大于后)则进行交换,这样,第一轮会将最大元素“”到数组右端,继续对剩余N-1个元素执行以上操作,每一轮获得一个最大元素,最终达到完全有序。

冒泡、选择、插入图解

void BubbleSort(int arr[], int len) 
{
     int i, j, temp;
     for (i = 0; i < len - 1; i++)//每一轮都将未排序数组的最大值交换到数组右边
     {
            for (j = 0; j < len - 1 - i; j++)//每一轮的未排序数组长度都在往前减小1
            {     
                  if (arr[j] > arr[j + 1]) 
                  {   //swap(arr[j], arr[j+1]);
                        temp = arr[j];
                        arr[j] = arr[j + 1];
                        arr[j + 1] = temp;
                  }
            }    
     }    
}

在这里插入图片描述

6.快速排序
思想:选择基准值将序列分成两部分,小于基准值的都放在左边,大于基准值的都放在右边。对于两部分,再分别递归的进行下去,直到所有序列排序。

快速排序图解

 void QuickSort(int arr[], int low, int high) //快排母函数
 {
   if (low < high) 
   {
     int pivot = Paritition(arr, low, high);//获取基准值下标,同时分成两部分
     QuickSort(arr, low, pivot - 1);
     QuickSort(arr, pivot + 1, high);
   }
 }
 Paritition(int arr[], int low, int high) 
 {
   int pivot = arr[low];//取基准值为第一个元素(临时值)
   while (low < high) 
   { //当队尾元素大于等于基准值是,向前移动指针
     while (low < high && arr[high] >= pivot) 
       --high;
     arr[low] = arr[high];//找到小于基准值的元素了,赋给low元素
     //当队首元素小于等于基准值,向后移动指针
     while (low < high && arr[low] <= pivot) 
       ++low;
     arr[high] = arr[low];//找到大于基准值的元素了,赋给high
   }
   //跳出循环时low=high,此时的low或high就是基准值的正确索引位置
   arr[low] = pivot;
   return low;
 }

在这里插入图片描述

7.归并排序
思想:分治策略,先分开后合并
1)申请临时空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;
lc88.合并两个有序数组-双指针
2)设定两个指针,最初位置分别为两个已经排序序列的起始位置,再设定一个指针指向合并序列;
3)比较两个指针所指向的元素,选择较小的元素放入到合并序列,并移动指针到下一位置;
4)重复步骤 3 直到某一排序序列的指针达到其序列尾;
5)将另一序列剩下的所有元素直接复制到合并序列尾。

归并排序图解

void MergeSort(int arr[], const int len) 
{
    int temp[len];//临时合并序列
    merge_sort_recursive(arr, temp, 0, len - 1);//无序序列、合并序列、首尾下标
}
void merge_sort_recursive(int arr[], int temp[], int start, int end) 
{
    if (start >= end)
        return;//递归结束,拆分完成,开始合并两部分的已排序序列
    int  mid = ((end - start) >> 1) + start;
    int start1 = start, end1 = mid;  //序列1的起点、终点指针
    int start2 = mid + 1, end2 = end;//序列2的起点、终点指针
    
    merge_sort_recursive(arr, temp, start1, end1);//不断拆分,直到两部分只包含1个元素
    merge_sort_recursive(arr, temp, start2, end2);//下一行开始合并,两部分越来越长
    
    int i = start;
    while (start1 <= end1 && start2 <= end2)//较小元素放入合并序列,直到某一序列末尾
        temp[i++] = arr[start1] < arr[start2] ? arr[start1++] : arr[start2++];
    //将另一序列中剩下的元素复制到合并序列     
    while (start1 <= end1)
        temp[i++] = arr[start1++];
    while (start2 <= end2)
        temp[i++] = arr[start2++];
    //将临时合并序列的元素拷贝回原数组    
    for (i = start; i <= end; i++)
        arr[i] = temp[i];
}

8.基数排序
思想:适合于有不同位数的大小数字!
先找十个桶:0~9
第一轮按照元素的个位数排序,依次放于桶中,之后,从所有桶中按照从左向右,从上到下的顺序依次取出元素,组成新的数组。
在新的数组中,进行第二轮,按照十位数排序,依次存放于桶中,再组成新数组。
在新的数组中,进行第三轮,按照百位数排序,依次存放于桶中,再组成新数组。
。。。。。。

基数排序图解

int maxbit(int data[], int n) //辅助函数,求数据的最大位数,也是排序轮数
{
    int maxData = data[0];              //初试最大数为第一个数
    //先求出最大数,再求其位数,这样与原先依次对每个数判断其位数相比,稍微优化点
    for (int i = 1; i < n; ++i)//遍历数组,保留找到最大数
    {
        if (data[i] > maxData)
            maxData = data[i];
    }
    int d = 1; //初始化最大数只有1位
    int p = 10;
    while (maxData >= p)//最大数字比10大,至少有2位(个位 + 十位)
    {
        maxData /= 10;
        ++d;
    }
    return d;//返回最大数字的位数
}
void RadixSort(int data[], int n) //基数排序
{
    int d = maxbit(data, n);//求出数组中最大数字的位数,及后续的排序轮数-每一位排一趟
    int *tmp = new int[n]; //临时数组
    int *count = new int[10]; //计数器,即用于存储的10个桶,代表0~9
    int i, j, k;
    int radix = 1;
    for(i = 1; i <= d; i++) //进行d次排序(最大数字有d位数)
    {
        for(j = 0; j < 10; j++)
            count[j] = 0;  //每次分配前清空计数器
        for(j = 0; j < n; j++)//遍历所有数字
        {
            k = (data[j] / radix) % 10; //每个数字的个位,下一轮则是十位,再下一轮百位
            count[k]++;//统计每个桶中装了多少数字
        }
        for(j = 1; j < 10; j++)
            count[j] = count[j - 1] + count[j]; //将tmp数组中的位置依次分配给每个桶
        for(j = n - 1; j >= 0; j--) //将所有桶中的数字依次收集到tmp数组中
        {
            k = (data[j] / radix) % 10;
            tmp[count[k] - 1] = data[j];
            count[k]--;
        }
        for(j = 0; j < n; j++) //将临时数组的内容复制到data中
            data[j] = tmp[j];
        radix = radix * 10;
    }
    delete []tmp;
    delete []count;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值