蓝桥杯备赛笔记(七)排序

1.冒泡排序

(1)冒泡排序的思想

冒泡排序的思想是每次将最大的一下一下运动到最右边,然后将最右边这个确定下来。再来确定第二个大的,再确定第三个大的...

对于数组a[],具体的来说,每次确定操作就是从左往右扫描,如果a[i] > a[i + 1],我们就执行swap(a[i],a[i+1])将两项交换,然后再往右检查,这样可以找出最大的并将其丢到最右边。

第一次确定操作是将a[i]~a[n]中最大的放到a[n];

第二确定操作是将a[1]~a[n - 1]中最大的放到a[n -1]。

依此类推,时间复杂度为O(n2)。由于排序过程中,数字像冒泡泡一样从左往右换过去,故名冒泡排序。

(2)冒泡排序的实现

冒泡排序一般用双重循环来实现。在这里i表示每次操作的右边界,也是存放当前操作最大值的位置。虽然j的范围是i - 1,实际上j + 1会到i,所以可以使得操作是正确的。

 #include
 using namespace std;
 const int N = 1e3 + 9;
 int a[N];
 ​
 int main()
 {
     int n;
     cin >> n;
     for(int i = 1;i <= n;++ i)
         cin >> a[i];
     //i表示当前要确定的位置
     for(int i = n;i >= 1;-- i)
     {
           //j从左往右扫
         for(int j = 1;j <= i -1;++ j)
         {
             if(a[j] > a[j + 1])
                 swap(a[j],a[j + 1]);
         }
     }
     //输出
     for(int i =1;i <= n; ++ i)
         cout << a[i] <<"\n"[i == n];
     return 0;
            
 }

2.选择排序

(1)选择排序的思想

选择排序的思想和冒泡排序类似,是每次找出最大的然后直接放到右边对应位置,然后将最右边这个确定下来(而不是一个一个地交换过去)。再来确定第二大的,再确定第三大的...

对于数组a[],具体的来说,每次确定操作(假设当前要确定的是i位置)就是从左往右扫描,计算出最大元素的下标max_id,最后执行一次swap(a[max_id], a[i])将两项交换即可。

第一次确定操作是将a[1]~a[n]中最大的放到a[n];

第二次确定操作是将a[1]~a[n -1]中最大的放到a[n -1]。

依此类推,时间复杂度为O(n2)

(2)选择排序的实现

选择排序一般用双重循环来实现。max_id表示最大元素的下标。这里要注意的细节是j的范围是[1,i],而在冒泡排序中j的范围是[1, i - 1]。

 #include
 using namespace std;
 const int N = 1e3 + 9;
 int a[N];
 ​
 int main()
 {
     int n;
     cin >> n;
     for(int i = 1;i <= n;++ i)
         cin >> a[i];
     //i表示当前要确定的位置
     for(int i = n;i >= 1;-- i)
     {
         int max_id = 1;//初始化为1
         //j从左往右扫求出max_id
         for(int j =1;j <= i;++ j)
         {
             if(a[j] > a[max_id])
                 max_id =j;
         }
         swap(a[max_id],a[i]);
     }
     //输出
     for(int i = 1;i <= n;++ i)
         cout << a[i] <<"\n"[i == n];
     return 0;
 }

3.插入排序

(1)插入排序的思想

插入排序是一种简单直观的排序算法,其基本思想是将待排序的元素逐个插入到已排序序列的合适位置中,使得已排序序列逐渐扩大,从而逐步构建有序序列,最终得到完全有序的序列,时间复杂度为O(n2)。

(2)插入排序的实现

插入排序一般用双重循环来实现。初始时我们认为长度为1的数组a[1]是有序的,然后将a[2]插入到合适的位置,使得a[1~2]有序,然后将a[3]插入,使得a[1 ~3]有序...直到a[1 ~n]有序。

 //i表示当前要确定的位置
 for(int i = 2;i <= n; ++i)
 {
     //此时[1, i - 1]已经为有序的数组
     int val = a[i], j;
     //将val和a[j - 1]比较,如果val < a[ j - 1],就将a[j - 1]往后移动一格,给val留出位置
     for(j = 1;j > 1 && val < a[j - 1];-- j)
     {
         a[j] = a[j - 1];
     }
     //当循环跳出时,j = 1或val >= a[j],且此时a[j]已经往后移动,此时的j为给val留出的位置
     a[j] = val;

 }

4.快速排序

(1)快速排序的思想

快速排序是一种基于分治法的排序方法,原理是将一个数组分成两个数组,其中一个子数组的所有元素都小于另一个子数组的元素,然后递归地对这两个子数组进行排序。

快速排序的思想是通过不断地将数组分为两个子数组,递归地对子数组进行排序,最终得到一个有序的数组。这个过程通过选择合适的基准和分区操作来实现。

快速排序拥有更好的时间复杂度O(nlog n),且不需要额外空间。

(2)快速排序的实现

 void QuickSort(int a[], int l, int r)
 {
     if(l < r)
     {
         int mid = Partition(a, l ,r);
         QuickSort(a, l, mid - 1);
         QuickSort(a, mid + 1, r);
     }
 }

这是快速排序的递归主体QuickSort()。传入参数为要排序的数组和区间的左右端点。

Partition函数会将数组a[l] ~ a[r]这个区间中某个基准数字放到正确的位置并将这个位置返回。

在确定了mid的位置之后,可以保证a[l] ~a[mid - 1]都< a[mid] < a[mid + 1] ~ a[r],于是只需要将左右两边分别向下递归地排序即可。

 int partition(int a[], int l, int r)
 {
     //设a[r]为基准,这一次partition会将a[r]放到正确位置上
     int pivot = a[r];
     //设两个下标i,j分别从l,r开始往中间走
     int i = 1,j = r;
     while(i < j)
     {
         while(i < j && a[i] <= pivot)
             i ++;//从上面循环出来后有i >= j或a[i] > pivot(说明找到了需要交换的位置)
         while(i < j && a[j] >= pivot)
             j --;//从上面循环出来后有i >= j或a[j] < pivot(说明找到了需要交换的位置)
         //如果i < j说明存在a[j] < pivot,否则就是a[r] <= pivot,a[i] >= pivot
         if(i < j)
             swap(a[i], a[j]);
         else
             swap(a[i], a[r]);
     }
     return i;
 }

5.归并排序

(1)归并排序的思想

归并排序和快速排序类似,也是一种基于分治法的排序方法。原理是将一个数组分成两个子数组,将子数组向下递归的排序后(当数组中仅有一个元素值无需再排序了,直接返回),得到两个有序数组,然后进行O(n)的合并,最终合并成有序的原数组。

归并排序拥有较好的时间复杂度o(nlog n),但需要额外的空间用于合并数组。

(2)归并排序的实现

 void MergeSort(int a[], int l, int r)
 {
     if(l == r)
         return;
     int mid = (l + r)/2;//注意这里会默认向下取整
     MergeSort(a, l, mid);
     MergeSort(a, mid + 1, r);
     //排序完成后a[l, mid]和a[mid + 1, r]都是分别有序的
     //将a[l, r]两部分一个个地放入到b[l, r]
     int pl = 1,pr = mid + 1,pb = 1;
     while(pl <+ ,mid || pr <= r)
     {
         if(pl > mid)
         {
             //左半边已经放完
             b[pb ++] = a[pr ++];
         }
         else if(pr > r)
         {
             //右半边已经放完
             b[pb ++] = a[pr ++];
         }
         else
         {
             //两边都还有元素,取个小的放到b里
             if(a[pl] < a[pr])
                 b[pb ++] = a[pl ++];
             else
                 b[pb ++] = a[ pr ++];
         }
     }
     //完成后复制回去
     for(inr i = 1;i <= r; ++ i)
         a[i] = b[i];
 }

这是归并排序的递归主体MergrSort()。传入参数为要排序的数组和区间的左右端点。

递归出口:当区间大小为1时,直接返回即可。

在确定了mid的位置之后,可以保证a[l] ~ a[mid]和a[mid + 1] ~ a[r]都是有序的,于是只需要扫一遍将两个有序组合并既可。

合并过程需要分类考虑一下,其中pl表示左半边的下标,pr表示右半边的下标,pb表示临时存放的数组下标。

合并完成后要将b复制回a。

你可能感兴趣的:(蓝桥杯C++,蓝桥杯,笔记,算法)