排序

排序

//主函数
#include
#include
int main()
{
    int arr[] = { 9, 58, 53, 78, 61, 81,23};
    int n = sizeof(arr) / sizeof(arr[0]);
    //bubbleSort(arr, n);
    //selectionSort(arr, n);
    //selSortSup(arr, n);
    //insertionSort(arr, n);
    //merSort(arr, n);
    //quickSort(arr, n);
    shellSort(arr, n);
    for (int i = 0; i < n; ++i)
    {
        printf("%d ", arr[i]);
    }
    printf("\n");

}
// 冒泡(优化)
//冒泡思想:每趟将未排序的数两两比较,找到当前未排元素中最大的
//优化:加上标志位,若没到n-1趟已排序好,只需遍历一遍发现没有再做交换即为有序,则退出循环
//时间复杂度为 O(N^2)  空间复杂度为 O(1) 稳定排序  
int* bubbleSort(int* A, int n) {
    for (int i = 0; i < n-1; i++)//n个数,最多需排 n-1趟
    {
        bool isOrder = true; //加标志位,判断是否排好序(即为遍历一遍不再发上交换)
        for (int j = 0; j < n - i-1; j++) //每一趟,相邻的数两两比较,升序a[j]>a[j+1]则交换,置标志位false
        {
            if (A[j] > A[j + 1])
            {
                int temp = A[j];
                A[j] = A[j + 1];
                A[j + 1] = temp;
                isOrder = false;
            }
        }
        if (isOrder == true)//有序,则跳出循环
        {
            break;
        }
    }
    return A;
}

// 选择排序
//选择排序思想,将最小的元素放到0位置,将次小的元素放到1位置上等等
//时间复杂度为 O(N^2)  空间复杂度为 O(1) 不稳定排序  
int* selectionSort(int* A, int n) {
    for (int i = 0; i < n - 1; i++) //选择第i小的数,依次找到最小、次小等
    {
        for (int j = i+1 ; j < n ; j++) //拿第i个数,和第i+1个数到第n个数作比较
        { 
            if (A[i] > A[j])  //a[i]>a[j]则交换
            { 
                int temp = A[i];
                A[i] = A[j];
                A[j ] = temp;
            }
        }
    }
    return A;
}

// 选择(优化)
//思想同选择排序,优化减少交换次数,遍历一遍,记录最小的值和下标,找到未排序中最小的再交换
//时间复杂度为 O(N^2)  空间复杂度为 O(1) 不稳定排序  
int* selSortSup(int* A, int n) {
    int i, j;
    int index = 0; //记录下标位置
     for (i = 0; i < n - 1; i++)
    {
        int min = A[i]; //先将最小值初值赋为a[i]
        int flag = false; //判断是否找到比a[i]更小的值,有则标志位true,无则false,不用交换
        for (j = i + 1; j < n; j++)
        {
            if (A[j] < min)
            {
                min = A[j];
                index = j;
                flag = true;
            }
        }
        if (flag) //标志位为true,则交换a[i]和a[index]
        {
             int temp = A[index];
            A[index] = A[i];
            A[i] = temp;
        }

    }
    return A;
}

// 插入排序
//思想即为从第i个元素(i从2开始)开始每个元素视为待插入元素,从第i-1个已排好的元素比较大小,如若a[j]>a[i],则a[j]后移,最终a[j+1]即为待插元素的位置
//时间复杂度为 O(N^2)  空间复杂度为 O(1) 稳定排序  
int* insertionSort(int* A, int n) 
{
    for (int i = 1; i < n; i++) //a[1...len-1]依次插入,i从1开始,因为第0个元素不需要排序
 {  
        int cur = A[i];  //提前保存待插入元素
        int j = i - 1;
        for (; j >= 0; j--) //从i-1到0寻找插入位置,从后往前查找避免元素覆盖的问题
        {
            if (A[j] > cur)
            {
                A[j + 1] = A[j];
            }
            else
            {
                break;
            }
        }
        A[++j] = cur; //a[j+1]即为待插元素的位置
    }
    return A;
}

// 归并排序
//思想是二路分治,将大规模的排序问题分成两个子问题,两个子问题再分成两个子问题,直到只剩一个
元素时不需要比较,再进行依次合并,最终使数字有序
//时间复杂度为 O(NlogN)  空间复杂度为 O(N) 稳定排序 
void merger_sort(int arr[],int start,int end);
void merger(int arr[],int start,int mid,int end);

void mer_sort(int arr[],int n)  //为了与前面的排序输入保持输入一致性
{
    merger_sort(arr,0,n-1);
}

void merger_sort(int arr[],int start,int end)
{
    if (start < end)
    {
        int mid = (start + end)/2;
        merger_sort(arr,start,mid);  //分成两个子问题,递归解决
        merger_sort(arr,mid+1,end);  //分成两个子问题,递归解决
        merger(arr,start,mid,end);   //合并
    }
}

void merger(int arr[],int start,int mid,int end)
{
    int *brr = (int *)malloc(sizeof(int) * (end - start + 1));  //开辟一个新空间,临时存放合并的元素
    int low1 = start;  //左半部分的左下标
    int high1 = mid;   //左半部分的右下标
    int low2 = mid+1;  //右半部分的左下标
    int high2 = end;   //右半部分的右下标
    int i = 0;

    while (low1 <= high1  &&  low2 <= high2)  //当两个部分都还有元素的时候
    {
        if (arr[low1] < arr[low2])  //两部分最左边的元素开始比较,小的则放到brr数组里
        {
            brr[i++] = arr[low1++];
        }
        else
        {
            brr[i++] = arr[low2++];
        }
    }
    while (low1 <= high1)  //当右半部分元素已完,则将左半部分元素依次放到brr数组里
    {
        brr[i++] = arr[low1++];
    }
    while (low2 <= high2) //当左半部分元素已完,则将右半部分元素依次放到brr数组里 
    {
        brr[i++] = arr[low2++];
    }

    int k = 0;  //brr下标
    for (int j=start;j<=end;++j)  //将brr临时存放的数据重新放到arr里
    {
        arr[j] = brr[k++];
    }    
}

// 快排
//快排的思想是以一个key为基准,将待排数分为两部分,分别为大于key的和小于key的,然后每部分进行递归,当每部分只有一个数时,排序结束
//时间复杂度为 O(NlogN) 空间复杂度为 O(NlogN) ~ O(N),与选择的划分值有关 不稳定排序  快排的常量系数较低,并不是比堆排序和归并排序优良,所以才叫快速排序
void quiSort(int* A, int l, int r);
int partion(int* A, int l, int r);

int* quickSort(int* A, int n) //为了与前面的排序输入保持输入一致性
{
    int l = 0;
    int r = n - 1;
    quiSort(A, l, r);
    return A;
}
void quiSort(int* A, int l,int r) //递归解决子问题
{
    int mid;
    if(l < r)
    {
        mid = partion(A,l,r);
        quiSort(A,l,mid-1);
        quiSort(A, mid+1, r);
    }
}
int partion(int* A,int l, int r) //划分过程
{
    int key = A[l]; //以第一个数为基准,也可以优化
    while (l < r)
    {
        while (A[r] >= key  && l < r)
        {
            r--;
        }
        A[l] = A[r];  //将右边小的放到左边
        while(A[l] <= key && l < r)
        {
            l++;
        }
        A[r] = A[l];  //将左边大的放到右边
    }
    A[l] = key;  //将最终的key值放到合适的位置
    return l;   //将key值返回,继续递归划分其他两个部分
}

// 希尔排序
//思想是插入排序改良的排序算法,步长大小的选择是希尔排序的关键,当插入排序基本有序时,是用直接排序较快,因此先让待排序数基本有序是希尔排序的想法
//时间复杂度为 O(NlogN) 空间复杂度为 O(1) 不稳定排序
int* shellSort(int* A, int n) {
    assert(A != NULL && n >= 0);
    int i, j, gap; 
    for (gap = n / 2; gap>0; gap /= 2) // 步长序列为n/2,n/4....直到1,排序完成
    {
        for (i = gap; i < n; i++)   //相当于将步长为1的插入排序改成步长为gap
        {
            for (j = i - gap; j >= 0; j -= gap)  //
            {
                if (A[j + gap] < A[j])
                {
                    int tmp = A[j + gap];
                    A[j + gap] = A[j];
                    A[j] = tmp;
                }
            }
        }
    }
    return A;
}

//堆排序
//思想是若升序则建立最大堆,交换arr[0]和arr[end],除去arr[end]再继续调整使成为最大堆,最终直至有序
//时间复杂度为 O(NlogN)  空间复杂度为 O(1) 不稳定排序 
void heap_adjust(int arr[],int start,int end);
void heap_sort(int arr[],int len)
{
    for (int i=len/2-1;i>=0;--i) //从最后一个节点的父节点开始调整
    {
        heap_adjust(arr,i,len-1);
    }
    int end = len-1;//最后一个元素的下标
    while(end > 0)
    {
        int tmp = arr[0];  //交换arr[0]和arr[end]
        arr[0] = arr[end];
        arr[end] = tmp;

        heap_adjust(arr,0,--end); //剩余元素堆调整
    }

}
void heap_adjust(int arr[],int start,int end)
{
    int temp = arr[start];
    for(int i=2*start+1;i<=end;i=2*start+1)  //左孩子下标为2*start+1,右孩子下标为2*start+2
    {
        if (2*start+2<=end && arr[2*start+1] < arr[2*start+2])
        {
            i++; //i下标里面保存的是左右孩子中最大值
        }
        if(arr[i] > temp) 
        {
            arr[start] = arr[i];
            start = i; //start继续往下走
        }
        else
        {
            break;
        }
    }
    arr[start] = temp;  //将保存的父节点的初始值赋给此时的节点值
}
  • 时间复杂度为O(n)的算法,不是基于比较的排序算法,思想来自于桶排序,主要包括基数排序和基数排序,空间复杂度为O(n)
//计数排序 
//思想是对于给定的输入序列中的每一个元素x,确定该序列中值小于x的元素的个数(此处并非比较各元素的大小,而是通过对元素值的计数和计数值的累加来确定),如若有相同元素,则算小于等于元素X的个数,从后往前遍历,遍历一次-1,保持排序的稳定性
//时间复杂度为O(n) 稳定排序  通常适用于范围较小的整数


伪代码如上

//基数排序
//思想是将个位进行排序,次序不变,再将十位进行排序,直到所有位排序完成,如果位数个数不等,可再前补0达成相同位数
//时间复杂度为O(n)  稳定排序  通常适用于位数相同的整数

//桶排序
//思想是、把区间[0,1)划分成n个相同大小的子区间,或称桶,然后将n个输入数分布到各个桶中去。桶间用插入排序,然后按次序把各桶中的元素列出来即可。
//时间复杂度为O(n)  稳定排序  通常适用于均匀分布的小数

  • 补充说明:

      概念:稳定性:指相同元素的值在排序前后的相对次序不发生变化
           内排序:指在排序期间数据对象全部存放在内存的排序。
           外排序:指在排序期间全部对象个数太多,不能同时存放在内存,必须根据排序过程的要求,不断在内、外存之间移动的排序。比如常见的有外归并排序。
           原地排序:是指不申请多余的空间来进行的排序,就是在原来的排序数据中比较和交换的排序
    
    1. 工程上的排序是综合排序;数据较小时选择插入排序;数据较大时选择快排或者其他O(NlogN)的排序
    2. 冒泡排序和选择排序与数组原始序列无关,时间复杂度都是O(N^2);快排和归并排序与数组原始序列也无关,时间复杂度为 O(NlogN)
  • 面试题:
  1. 已知一个几乎有序的数组,几乎有序是指,如果把数组排好序的话,每个元素移动的距离不超过k,并且k相对于数组长度来说很小。请问选择什么方法对其排序较好?
      思路:冒泡排序、选择排序、快排和归并排序与数组原始序列无关,计数和基数排序对数据特征有要求,不具有普遍性,插入排序O(N*K),改良的堆排序O(NlogK)
  2. 判断数组中是否有重复值。必须保证额外空间复杂度为O(1)
      思路:如果不限制空间复杂度可以用哈希表,限制空间复杂度的话需要排序
  3. 把两个有序数组合并为一个数组,第一个数组空间正好可以容乃两个数组的元素
      思路:从后往前比较,避免覆盖
  • 总结

    原地排序   ▪ 希尔排序 ▪ 冒泡排序 ▪ 插入排序 ▪ 选择排序 ▪ 堆排序
    稳定排序   ▪ 冒泡排序 ▪ 插入排序 ▪ 归并排序 ▪ 计数排序 ▪基数排序
    不稳定排序  ▪ 堆排序 ▪ 快速排序 ▪ 希尔排序 ▪ 选择排序

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值