面试题精选(排序算法类)c/c++版 上篇

1-1排序算法专题

0 常见排序算法总结

基础排序算法: 冒泡排序、选择排序、插入排序归并排序、希尔排序、快速排序堆排序

 

建议看不懂原理说明或图示时请看代码。

1. 冒泡排序

1.1 基本思想

冒泡排序是一种交换排序,它的基本思想是:两两比较相邻记录的关键字,如果反序则交换,直到没有反序的记录为止。

算法描述:

  1. 比较相邻两个数据如果。第一个比第二个大,就交换两个数
  2. 对每一个相邻的数做同样1的工左,这样从开始一队到结尾一队在最后的数就是最大的数。
  3. 针对所有元素上面的操作,除了最后一个。
  4. 重复1~3步骤,知道顺序完成。

1.2 图解

 

1.3 代码

/*
    1. 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
    2. 对第0个到第n-1个数据做同样的工作。这时,最大的数就“浮”到了数组最后的位置上。
    3. 针对所有的元素重复以上的步骤,除了最后已经选出的元素(有序)。
    4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较.
*/
// 稳定排序,平均 O(n**2),最好 O(n), 最差 O(n**2),辅助空间 O(1)
void BubbleSort(vector<int> &nums)
{
    int n = nums.size();
    if (n==0) return;
    for (int i=0;i<n;i++)
    {
        for (int j=0;j<n-1-i;j++)
        {
            if (nums[j] > nums[j+1])
            {
                // swap(nums[j], nums[j+1]);
                int temp = nums[j];
                nums[j] = nums[j+1];
                nums[j+1] = temp;
            }
        }
    }
}

2. 选择排序

2.1 基本思想

选择排序(Select Sort) 是直观的排序,通过确定一个 Key 最大或最小值,再从带排序的的数中找出最大或最小的交换到对应位置。再选择次之。双重循环时间复杂度为 O(n^2)

算法描述:

  1. 在一个长度为 N 的无序数组中,第一次遍历 n-1 个数找到最小的和第一个数交换。
  2. 第二次从下一个数开始遍历 n-2 个数,找到最小的数和第二个数交换。
  3. 重复以上操作直到第 n-1 次遍历最小的数和第 n-1 个数交换,排序完成。

2.2 图解

 

2.3 代码

/*
    1. 在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。
    2. 再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
    3. 以此类推,直到所有元素均排序完毕。
*/

// 不稳定排序,平均 O(n**2),最好 O(n**2), 最差 O(n**2),辅助空间 O(1)

void SelectSort(vector<int> &nums)
{
    int n = nums.size();
    if (n==0) return;
    for (int i=0;i<n-1;i++)
    {
        int idx = i; //每一趟循环比较时,idx用于存放较小元素的数组下标,这样当前批次比较完毕最终存放的就是此趟内最小的元素的下标,避免每次遇到较小元素都要进行交换。
        for (int j=i+1;j<n;j++)
        {
            if (nums[idx] > nums[j]);
            {
                idx = j;
            }
        }
        if (idx !=i)
        {
            int temp = nums[idx];
            nums[idx] = nums[i];
            nums[i] = temp;
        }
    }
}

3. 插入排序(重要)

3.1 基本思想

将一个数插入一个已经排好序的数据中。

  1. 第一次循环时,从第2个数开始处理。我们将第1个数作为已经排好序的数据:当第2个数 > 第1个数时,将第2个数放在第1个数后面一个位置;否则,将第2个数放在第1个数前面。此时,前两个数形成了一个有序的数据。
  2. 第二次循环时,我们处理第3个数。此时,前两个数形成了一个有序的数据:首先比较第3个数和第2个数,当第3个数 > 第2个数时,将第3个数放在第2个数后面一个位置并结束此次循环;否则,再和第1个数比较。如果第3个数 > 第1个数,则将第3个数插入第1个数和第2个数中间;否则,第3个数 < 第1个数,则将第3个数放在第1个数前面。此时,前三个数形成了一个有序的数据。
  3. 后续的数据同理处理,直至结束。

3.2 图解

 

3.3 代码

/*
   直接插入排序基本思想是每一步将一个待排序的记录,插入到前面已经排好序的有序序列中去,直到插完所有元素为止。
   1. 从第一个元素开始,该元素可以认为已经被排序
   2. 取出下一个元素,在已经排序的元素序列中从后向前扫描
   3. 如果被扫描的元素(已排序)大于新元素,将该元素后移一位
   4. 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
   5. 将新元素插入到该位置后
   6. 重复步骤2~5
*/
// 稳定排序,平均 O(n**2),最好 O(n), 最差 O(n**2),辅助空间 O(1)
void InsertSort(vector<int> &nums)
{
    int n = nums.size();
    if (n==0) return;
    // 从下标为1的元素开始选择合适的位置插入,因为下标为0的只有一个元素,默认是有序的
    for (int i=1;i<n;i++)
    {
          // 记录要插入的数据
          int temp  = nums[i];
          int j = i- 1;
          //与已排序的数逐一比较,大于temp时,该数移后
          while (j>=0) && (temp < nums[j])
          {
                nums[j+1] = nums[j];
                j--;
          }
          nums[j+1] = temp;
    }
}

另外小编还整理了一些各大知名企业BAT精选面试题、需要的朋友可以扫码获取

 

4. 归并排序(重要)

4.1 基本思想

归并排序的主要思想是分治法。主要过程是:

  1. 将n个元素从中间切开,分成两部分。(左边可能比右边多1个数)
  2. 将步骤1分成的两部分,再分别进行递归分解。直到所有部分的元素个数都为1。
  3. 从最底层开始逐步合并两个排好序的数列。

4.2 图解

 

4.3 代码

/*
   将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。归并排序是一种稳定的排序方法。
   
1、确定数组的大小,以及输入数组中的元素值;
2、将输入的数组进行分组归并;
3、将整个数组分成左右两个数组,左右两个数组再向下分,直至子数组的元素少于2个时,子数组将停止分割;
4、当左右子数组不能再分割,也是都是一个元素时,比较他们的大小,进行排序合并;
5、再排序合并上一级子数组为两个元素的数组,接着再排序合并上一级子数组为四个元素的数组;直至到排序合并刚开始的两个子数组,最后成为拍好序的数组;
*/
// 稳定排序,平均 O(nlogn),最好 O(nlogn), 最差 O(nlogn),辅助空间 O(n)
void merge(int array[],int first,int last)
{
    int temp[last+1];
    int mid=(first+last)/2;
    int i=0; //临时数组指针
    int l=first;//左序列指针
    int r=mid+1;//右序列指针
    while(l<=mid&&r<=last)//sort the ele of the left array and the right array
    {
        if(array[l]<array[r])
            temp[i++]=array[l++];
        else
            temp[i++]=array[r++];
    }
    while(l<=mid) //将左边剩余元素填充进temp中
    {
        temp[i++]=array[l++];
    }
    while(r<=last) //将右序列剩余元素填充进temp中
    {
        temp[i++]=array[r++];
    }
    i=0;
    //将temp中的元素全部拷贝到原数组中
    while(first<=last)
    {
        array[first++]=temp[i++];
    }    
}
void mergesort(int data[],int first,int last)
{
    if(first<last)
    {
        int mid=(first+last)/2;
        mergesort(data,first,mid);  //左边归并排序,使得左子序列有序
        mergesort(data,mid+1,last); //右边归并排序,使得右子序列有序
        merge(data,first,last); //将两个有序子数组合并操作
    }
}

5. 希尔排序

5.1 基本思想

希尔排序,也被称为递减增量排序,是简单插入排序的一种改进版本。

  • 在插入排序中,如果待排序列中的某个元素,距离有序数列中待插入位置非常远,就需要比较很多次才可以到达插入位置,这是因为待插入元素局部非常无序,比如说[2, 3, 4, 5, 6, 7, 8, 1, ...],我们要插入1,就必须将1和前面的2-8每个值都比较一下,就是因为1附近非常无序,想象一下,如果待插入元素附近比较有序,那么在进行插入排序的时候就只需要比较非常少的几次就可以插入到正确位置。
  • 希尔排序就是先把整个序列排得相对比较有序,再进行插入排序的时候,需要比较的次数就会变得很少。
  • 插入排序的增量(间隔)为1,希尔排序相当于将这个间隔从最大为数组长度的一半一直降到1,这一点在程序中体现的很清楚。当间隔很大时,比较的次数也会很少,在进行了几次大间隔的插入排序后,序列已经部分有序,这样再进行小间隔的插入排序也自然会比较很少的次数。
  • 希尔排序就是将处在相同间隔的元素提取出来单独进行插入排序,然后逐步将间隔减小到1的过程。

5.2 图解

 

 

5.3 代码

/*
   数组列在一个表中并对列分别进行插入排序,重复这过程,不过每次用更长的列(步长更长了,列数更少了)来进行。
*/
// 不稳定排序,平均 O(nlogn)-O(n^2),最好 O(nlogn), 最差 O(n**2),辅助空间 O(1)
void ShellSort(int a[],size_t n)
{
    int i,j,k,group;
    for (group = n/2; group > 0; group /= 2)//增量序列为n/2,n/4....直到1
    {
        for (i = 0; i < group; ++i)
        {
            for (j = i+group; j < n; j += group)
            {
                //对每个分组进行插入排序
                if (a[j - group] > a[j])
                {
                    int temp = a[j];
                    k = j - group;
                    while (k>=0 && a[k]>temp)
                    {
                        a[k+group] = a[k];
                        k -= group;
                    }
                    a[k] = temp;
                }
            }
        }
    }
}

 

6. 快速排序(重要)

6.1 基本思想

快速排序由C. A. R. Hoare在1962年提出。它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

主要过程:

  1. 在待排序的元素任取一个元素作为基准(通常选第一个元素,称为基准元素)
  2. 将待排序的元素进行分块,比基准元素大的元素移动到基准元素的右侧,比基准元素小的移动到作左侧,从而一趟排序过程,就可以锁定基准元素的最终位置
  3. 对左右两个分块重复以上步骤直到所有元素都是有序的(递归过程)

6.2 图解

 

6.3 代码

/*
    1. 从数列中挑出一个元素作为基准数。
    2. 重新排序数列,将比基准数大的放到右边,小于或等于它的数都放到左边。
    3. 再对左右区间递归执行第二步,直至各区间只有一个数。
*/
// 不稳定排序,平均 O(nlogn),最好 O(nlogn), 最差 O(n**2),辅助空间 O(logn)
int Paritition (int A[], int low, int high) 
{
    int pivot = A[low];
    while (low < high) 
    {
        while (low < high && A[high] >= pivot) 
        {
            --high;
        }
        A[low] = A[high];
        while (low < high && A[low] <= pivot) 
        {
            ++low;
        }
        A[high] = A[low];
    }
    A[low] = pivot;
    return low;
}
void QuickSort(int A[], int low, int high) //快排母函数
{
    if (low < high) 
    {
        int pivot = Paritition1(A, low, high);
        QuickSort(A, low, pivot - 1);
        QuickSort(A, pivot + 1, high);
    }
}

精简版

void QuickSort(vector<int> &nums, int low, int high)
{
    if(low < high)
    {
        int l = low, r = high, pivot = nums[low];
        while(l<r)
        {
            while(l<r && nums[r] >=pivot) // 从右向左找第一个小于基准的数
            {
                r--;
            }
            nums[l] = nums[r];
            while(l<r && nums[l] < pivot) // 从左向右找第一个大于等于基准的数
            {
                l++;
            }
            nums[r] = nums[l];
        }
        nums[l] = pivot;
        quick_sort(nums, low, l-1);
        quick_sort(nums, l+1, high);
    }
}

7. 对排序(重要)

7.1 基本思想

堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。可以利用数组的特点快速定位指定索引的元素。堆分为大根堆和小根堆,是完全二叉树

  • 完全二叉树:除了最后一层之外的其他每一层都被完全填充,每一层从左到右的填充数据,不能空缺
  • 大根堆:任意一个节点的值均大于等于它的左右孩子的值,位于堆顶的节点值最大
  • 小根堆:任意一个节点的值均小于等于它的左右孩子的值,位于堆顶的节点值最小

 

堆排序的基本思想是:将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了

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

7.2 图解

 

7.3 代码

/*
堆排序的基本思想是:将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了
1.将无需序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆;
2.将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端;
3.重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。
*/
// 不稳定排序,平均 O(nlogn),最好 O(nlogn), 最差 O(n**2),辅助空间 O(1)
void adjustHeap(int nums[],int i,int length)
{
    int temp = arr[i];//先取出当前元素i
    for(int k=i*2+1;k<length;k=k*2+1)     //从i结点的左子结点开始,也就是2i+1处开始
    {
        if(k+1<length && arr[k]<arr[k+1])    //如果左子结点小于右子结点,k指向右子结点
        {
            k++;
        }
        if(arr[k] >temp)    //如果子节点大于父节点,将子节点值赋给父节点(不用进行交换)
        {    
            arr[i] = arr[k];
            i = k;
        }
        else
        {
            break;
        }
    }
    arr[i] = temp;    //将temp值放到最终的位置
}

void HeapSort(int nums[],int len)
{
    //1.构建大顶堆
    for(int i=len/2-1;i>=0;i--)
    {
        //从第一个非叶子结点从下至上,从右至左调整结构
        adjustHeap(nums,i,len);
    }
    //2.调整堆结构+交换堆顶元素与末尾元素
    for(int j=len-1;j>0;j--)
    {
        swap(nums[0],nums[j]);//将堆顶元素(最大值)与末尾元素进行交换,将最大值交换到数组的最后位置保存
        adjustHeap(nums,0,j);//重新对堆进行调整
    }
}
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页