面试复习-------算法与数据结构------排序

(1)快速排序

最坏O(n^2),平均O(n*logn);递归需要函数栈,空间复杂度为:O(nlogn)

特点:每趟都选择一个基准数,排完使左边全部比基准小,右边全部比基准大

快排的隐含因子比较小,所以一般是首要选择

核心的partition函数如下:

int Partition(int* arr, int left, int right)
{
    if(left >= right)
        return left;
    int tmp = arr[left];    //取第一个数为枢纽
    int i = left;
    int j = right;          //i,j表示循环的范围

    while (i<j)
    {
        while(arr[j] >= tmp && j > i)  j--;      //从右往左找第一个比枢纽小
        if(j > i)                               //********需要判断是否找到
            swap(arr[i++],arr[j]);
        while(arr[i] < tmp && j > i)  i++;
        if(i < j)
            swap(arr[i],arr[j--]);

    }
    arr[i] = tmp;               //枢纽写入,返回下标索引
    return i;
}

递归调用完成循环:

void fastSort(int* arr, int left, int right)
{
    if(arr == NULL || right <= left )
        return ;
    int index = Partition(arr, left, right);

    fastSort(arr, left, index-1);
    fastSort(arr, index+1, right);

}


快排的变形:

寻找第k小的数:生成枢纽,如果枢纽索引 < k,则在右半区间找;否则左半区间;直至枢纽索引=k。(O(n) )

寻找最小的k个数剑指offer30):

解一(数组可改):按照①中的方法,找到k,那么左边的k-1个数就是要求的

解二(数组不可改变,处理海量数据(因为每次只需读一个)):声明大小k数组,使其最大堆排序;

-------------------遍历海量数组(O(n));

--------------------------找到堆中最大的数(O(1));

--------------------------------进行比较后,可能会有删除,插入,调整(O(log k))。

-----------------------------------------时间复杂度(O(n log k))

数组中出现次数超过一半的数字剑指offer29):

解一(数组可改):还是①中的变形,第n/2大的数字(中位数)就是出现次数超过一半的数字(最后统计次数检查一下符不符合要求,因为很有可能不存在这样的数)

解二(数组不可改):次数最多的数比其他所有数次数加起来还多。可以遍历记录一个数次数,如果后面的数等于前一个数,则次数加一,否则次数减一;如果减到0,则换成记录下一个数。那么,遍历完了之后,记录的那个数就是所求的。(同样也要检查是否不存在这样的数)


(2)插入排序

最坏O(n^2);平均O(n^2);最好O(n)

void insertSort(int* arr, int n)
{
    if(arr == NULL || n <= 0)
        return ;
    for(int i = 1; i < n ; ++i){
        int tmp = arr[i];
        int j = i-1;
        while(arr[j] > tmp && j >= 0){  //注意条件j>=0
            arr[j+1] = arr[j];      //依次往前移
            j--;
        }
        arr[j+1] = tmp;     //最后是j+1
    }
}
(3) 选择排序

最坏O(n^2);平均O(n^2);

void selectSort(int* arr, int n)
{
    if(arr == NULL || n <= 0)
        return ;
    for(int i = 0 ; i < n ; i++){       //循环n次,每次从未排序选最小
        int min_dex = i;
        for(int j = i+1; j < n; j++){
            if(arr[j] < arr[min_dex]){
                min_dex = j;            //记录最小值的索引
            }
        }
        if(min_dex != i)
            swap(arr[i], arr[min_dex]);
    }
}
(4) 冒泡排序

平均O(n^2);最好O(n)

void bubbleSort(int* arr, int n)
{
    if(arr == NULL || n <= 0)
        return ;
    for(int i = 0; i < n; ++i){
        bool change =false;
        for(int j = n-1; j > i ; --j){  //从后往前循环上浮
            if(arr[j] < arr[j-1]){
                swap(arr[j],arr[j-1]);
                change = true;
            }
        }
        if(change == false)     //若某一趟中没有发生交换,说明已经有序
            break;
    }
}
(5) 归并排序

常用于海量数据的外排序,O(n*log n)
思想:

-----将n个元素分成两个n/2子序列;
-----分别对两个子序列执行归并排序

-----合并两个已排序的子序列

上述思想的递归实现如下:

void mergeSort(int* arr, int left, int right)
{
    if(arr == NULL || left >= right)
        return ;
    int mid = left + (right - left)/2 ;
    mergeSort(arr, left, mid) ;
    mergeSort(arr, mid+1, right);

    merge(arr, left, mid, right);
}
其中,实现两个有序数组的排序的merge函数如下,用到了O(n)的辅助空间:

void merge(int* arr, int left, int mid, int right)
{
    int* tmp = new int[right - left +1];
    int index1 = left, index2 = mid+1, tmp_index = 0;

    while(index1 <= mid && index2 <= right){
        if(arr[index1] < arr[index2]){
            tmp[tmp_index] = arr[index1];
            tmp_index++;
            index1++;
        }else{
            tmp[tmp_index] = arr[index2];
            tmp_index++;
            index2++;
        }
    }
    if(index1 > mid && index2 <= right)
    {
        while(index2 <= right){
            tmp[tmp_index] = arr[index2];
            tmp_index++;
            index2++;
        }
    }
    if(index1 <= mid && index2 > right)
    {
        while(index1 <= mid){
            tmp[tmp_index] = arr[index1];
            tmp_index++;
            index1++;
        }
    }

    for(int i = left; i <= right; ++i){
        arr[i] = tmp[i-left];
    }

    delete[] tmp;
}
归并排序的变形:

①合并两个排序的链表(剑指offer17

典型的递归过程解决,考虑空指针的问题;

②寻找数组中的逆序对(剑指offer36

按照上述归并排序的思想:

-----将n个元素分成两个n/2子序列;
-----分别对两个子序列执行归并排序,并且统计各自逆序对的个数left,right

-----合并两个已排序的子序列,并且统计两个子序列之间逆序对的个数count

最终结果为count+left+right;

在求count的时候,利用两个子序列已排序的特点,如果arr[index1]>arr[index2],那么index2和第二个子序列中index1之后的都构成了逆序,即count += mid- index1+1;

注意,此时不能算index1与index2之前的都是逆序,因为下面被拿走的是index2(归并排序每次拿走较小的一个),算index2不会有重复,算index1会有重复。

(6) 堆排序(大根堆)

调整过程heapAdjust:

假设节点i的左右子树都是大根堆,而节点i为根的不是大根堆,按照以下步骤调整:

1.从arr[i],arr[left(i)],arr[right(i)]选出最大值arr[largest],这个值将是最终的根节点。

2.如果arr[i]是最大的,则无需调整;否则,将arr[i]和arr[largest]交换,然后,交换的节点需要依次向下进行调整;

该过程的递归实现如下:

void heapAdjust(int* arr, int index, int size)
{
    if(arr == NULL || size < 0 )
        return ;
    int largest = index ;
    int left = 2*index + 1;
    int right = 2 * index + 2;
    if(left < size && arr[left] > arr[largest])
        largest = left;
    if(right < size && arr[right] > arr[largest])
        largest = right;
    if(largest != index)
    {
        swap(arr[index], arr[largest]);
        heapAdjust(arr, largest, size);
    }
}
heapAdjust的迭代版本:

void heapAdjust(int* arr, int index, int size)
{
    if(arr == NULL || size < 0 )
        return ;
    int tmp = arr[index];

    for(int i = 2*index+1;i<size;i=2i+1){
        if(i < size-1 && arr[i] < arr[i+1]){    //有右孩子且右孩子较大
            i++;    //i保存左右孩子中较大者
        }
        if(tmp > arr[i]){
            break;      //无需调整
        }
        arr[index]=arr[i];
        index = i;
    }
    arr[index]=tmp;

}

建立大根堆buildHeap:

根据要求,用给定的数组arr[0,1,....,size-1]建立大根堆,可以从最下面的非叶节点依次向上调用heapAdjust函数,这样,当调整到arr[0]的时候,整棵数就是一个大根堆:

void heapBuild(int* arr, int size)
{
    if(arr == NULL || size <= 0)
        return ;
    
    for(int i = size/2-1; i >= 0; i--)
        heapAdjust(arr, i, size);
}

进行堆排序heapSort:

为了进行原地排序,引入变量heap_size表示堆的大小,于是数组arr[0…size-1]中,arr[0…heap_size-1]为堆,arr[heap_size, size-1]为排好序的元素。

排序的过程:

①初始heap_size = size;用整个数组建立大根堆:heapBuild(arr, heap_size);

②交换arr[0]和arr[size-1],将最大值放到arr最后;heap_size--;

③交换之后,最大堆的性质可能已经被破坏,调用heapAdjust(arr, 0, heap_size);进行调整

④重复上述过程,直到堆大小为1

代码如下:

void heapSort(int* arr, int size)
{
    if(arr == NULL || size <= 0)
        return ;
    int heap_size = size;
    heapBuild(arr, heap_size);

    while(heap_size > 1){
        swap(arr[0],arr[heap_size-1]);
        heap_size--;
        heapAdjust(arr, 0, heap_size);
    }
}

---------------------------------------------------------------------

分界线

--------------------------------------------------------------------

上面的几种排序方法都是基于比较的排序

基于比较的排序方法中,堆排序和归并排序都是渐进最优(O(n*log n)),下面给出一些线性时间的排序算法

(1)计数排序

对于排序元素是正整数,且元素范围不大的情况下适用

(2)基数排序

按照数位进行排序

(3)桶排序

将数组分到有限数量的桶里。每个桶再个别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序)。最后按次序把各个桶里的元素列出来即可。

例子:leetcode164----无序数组排序后相邻元素差值的最大值

解题思路:将这个数组放入n+1个桶中,最后遍历找“空桶”

优化:设置最大桶和最小桶,则每个区间只需要保存两个值

桶排序的性能分析

①对于N个待排数据,M个桶,如果相对于同样的N,桶数量M越大,其效率越高,最好的时间复杂度达到O(N)。当然桶排序的空间复杂度为O(N+M),如果输入数据非常庞大,而桶的数量也非常多,则空间代价无疑是昂贵的。
②桶排序是稳定的(如果桶内数据的排序算法选择的不是稳定的,桶排序就变成不稳定的)。

适用场景
1、输入数据符合均匀分布。
2、在面试的海量数据处理题目中,如对每天数以亿计的数据进行排序,直接排序即使采用nlgn的算法,依然是一件很恐怖的事情,内存也无法容纳如此多的数据,这时桶排序就可以有效地降低数据的数量级,再对降低了数量级的数据进行排序,可以得到比较良好的效果。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值