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

1、插入排序

思想:每一步将待排序的元素,按照其排序码的大小,插入到前面已经排好序的一组元素的合适位置上面,直到元素全部插完为止。
当插入第i(i>=1)个元素时,前面的arr[1],arr[2]……arr[i-1]都已经是排好序的,此时用arr[i]的排序码和之前的ar[1],arr[2]……arr[i-1]进行比较,放入合适的位置,将原来位置上的元素顺序后移。
元素集合越接近有序,直插排序算法的时间效率就越高。
最优情况下:时间复杂度为:O(n);
最坏情况下:时间复杂度为:O(n^2);
空间复杂度:O(1),是一种稳定的排序算法 。
实现代码如下:

void InsertSort(int arr[], int size)//插入排序
{
     int i = 0;
     for (i = 1; i < size; i++)//外部排序
     {
          int key = arr[i];//插入第i个元素时
          int j = 0;
          for (j = i - 1; j >= 0; j--)//内部排序
          {
              if (arr[j] <= key)
                   break;
              else
                   arr[j + 1] = arr[j];//后移
          }
          arr[j + 1] = key;
     }
}

2、选择排序

思想:分为两头,left存放最小的元素,right存放最大的元素,寻找最小最大的元素需要在[left,right]这个闭区间里面寻找,找到之前就存放在相应的位置。然后left++,right–;逐渐缩小查找的范围,直到left>right循环结束。
要特别注意一点:当最大值的下标刚好是left,那么将最小值移到left位置时,将会把最大值换走,所以此时应该保存最大值的下标:max=min;
代码如下:

void SelectSort(int arr[],int size)//选择排序
{
     int left = 0;
     int right = size - 1;
     int min;
     int max;
     while (left < right)
     {
          min = left;//注意,一定不能 初始化为0,否则后面的比较会把原先选出来的最值带入比较
          max = right;
          for (int i = left; i <= right; i++)
          {
              if (arr[i]>arr[max])//找到最大值
                   max = i;
              if (arr[i] < arr[min])//找到最小值
                   min = i;
          }
          swap(&arr[left], &arr[min]);
          if (max == left)//判断如果最大值解释最左边元素的情况,因为换最小值的时候就被换走了,所以需要重新赋值
              max = min;
          swap(&arr[right], &arr[max]);
          left++;//缩小范围
          right--;
     }
}

3、希尔排序

思想:希尔排序是对直接插入排序的优化

在gap的间隔的元素直插排序,目的是为了让数组的元素基本有序,然后再改变gap的大小,再次排序。
代码如下:

void ShellSort(int arr[], int size)//希尔排序
{
     int gap = size;
     int i = 0;
     int j = 0;
     int g = 0;
     while (gap > 1)
     {
          gap = gap / 3 + 1;
          for (g = 0; g < gap; g++){
              for (i = gap + g; i < size; i+=gap)
              {
                   int key = arr[i];
                   for (j = i - gap; j >= 0; j-=gap)
                   {
                        if (arr[j] <= key)
                             break;
                        else
                             arr[j + gap] = arr[j];
                   }
                   arr[j + gap] = key;
              }
          }
     }
}

代码优化如下:

void ShellSort1(int arr[], int size)//希尔排序简化版
{
     int gap = size;
     int i = 0;
     int j = 0;
     while (gap > 1)
     {
          gap = gap / 3 + 1;
          for (i = gap; i < size; i++)
          {
              int key = arr[i];
              for (j = i - gap; j>=0; j -= gap)
              {
                   if (arr[j] <= key)
                        break;
                   else
                        arr[j + gap] = arr[j];//后移
              }
              arr[j + gap]=key;
          }
     }
}

4、冒泡排序

思想 :冒泡排序是我们接触最早的一种排序了,分为两层循环,外层循环是负责控制内层循环的范围,内存循环主要是将最大的元素放在最后面。
有一个优化的地方 很重要,就是设置一个flag,将其初始值设为0,每当进行交换后就将其值置为1,如果循环后发现flag为0,说明后面就已经有序了,循环不需要进行下去了,break退出。
代码如下:

void BubbleSort(int arr[], int size)//冒泡排序
{
     int i;
     int j;
     int flag;//这次冒泡过程中是否有数据交换
     for (i = 0; i <= size-1; i++)//代表需要循环的趟数
     {

            flag = 0;
          for (j = 0; j <size - i-1; j++)//每一趟 需要比较的次数
          {
              if (arr[j] > arr[j + 1])
              {
                   swap(&arr[j+1], &arr[j]);
                   flag = 1;
              }
          }
         if (flag == 0)//表示没有数据交换了,表示剩下的数据有序
     {
          return;

     }
     }

}

5、快速排序

思想:任取待排序序列的某个元素 作为基准值,按照排序码,将序列分为两个部分,左序列的元素均小于基准值,右序列的元素均大于基准值,然后左右子序列复制该过程,直到所有的元素都排列到相应的位置为止。左右切割。
分为3种方法:

1、左右下标法


代码如下:

int SortPart1(int arr[], int left, int right)//快排第1种方法
{
     int key = arr[right];
     int begin = left;//从前往后找第一个比key大的数,找到交换
     int end = right;//从后往前找到第一个比key小的数,找到交换
     while (begin < end)
     {
          while ((begin < end) && arr[begin] <= key)//反例:555555,如果没有=,就需要一直交换,破坏稳定性
          {
              begin++;
          }
          while ((begin<end) && arr[end]>=key)
          {
              end--;
          }
          if (begin < end)
          {
              swap(&arr[end], &arr[begin]);
          }
     }
     swap(&arr[begin], &arr[right]);
     return begin;
}
void SortFercious(int arr[], int left, int right)
{
     if (left >= right)
          return;
     int div = SortPart1(arr, left, right);
     SortFercious(arr, left, div - 1);
     SortFercious(arr, div + 1, right);
}
void QuickSort(int arr[], int size)
{
     SortFercious(arr, 0,size - 1);
}

2、挖坑法


代码如下:

int SortPart2(int arr[], int left, int right)//挖坑法,快排第二种方法
{
     int key = arr[right];
     int begin = left;
     int end = right;
     while (begin < end)
     {
          while ((begin < end) && arr[begin] < key)
          {
              begin++;
          }
          arr[end] = arr[begin];
          while ((begin < end) && arr[end] > key)
          {
              end--;
          }
          arr[begin] = arr[end];
     }
     arr[begin] = key;
     return begin;
}

3、前后下标法

思想:

代码如下:

int SortPart3(int arr[], int left, int right)//第三种方式,前后下标法
{
     int key = arr[right];
     int prev = left;
     int cur = left + 1;
     while (cur <= right)
     {
          while (prev != cur)
          {
              if (arr[cur] < arr[prev])
                   swap(&arr[cur], &arr[prev]);
              prev++;
          }
          cur++;
     }
     swap(&arr[prev], &arr[right]);
     return prev;
}

6、归并排序
思想:是将待排序的元素都看作是有序的,再按照将有序链表的归并起来就和很容易了。

void Merge(int arr[], int left, int mid, int right, int extra[])//归并,假定要归并的两个序列本身就是有序的
{
      int i = left;
      int il = left;
      int ir = mid;
      for (; il < mid&&ir <= right; i++)
      {
           if (arr[il] <= arr[ir])
           {
               extra[i] = arr[il];
               il++;
           }
           else
           {
               extra[i] = arr[ir];
               ir++;
           }
      }
      while (il < mid)
      {
           extra[i++] = arr[il++];
      }
      while (ir <= right)
      {
           extra[i++] = arr[ir++];
      }

     for (int i = left; i <= right; i++)
     {
          arr[i] = extra[i];
      }
}
void MergeSortLoop(int array[], int size, int extra[])
{
     int gap;
     int i;
     // 表示需要合并多少层
     for (gap = 1; gap < size; gap = 2 * gap) {
          // 每层需要合并多少次
          for (i = 0; i < size; i += 2 * gap) {
              int left = i;
              int mid = i + gap;
              int right = mid + gap - 1;
              if (mid >= size) {
                   break;
              }
              if (right >= size) {
                   right = size - 1;
              }
              Merge(array, left, mid, right, extra);
          }
     }
}
void MergeSort(int arr, int size)
{
     int *extra = (int *)malloc(sizeof(int)*size);
     assert(extra);
     //Recursion(arr, 0, size, extra);
     MergeSortLoop(arr, size, extra);
     free(extra);
}

7、堆排序

堆排序最重要的就是建堆和向下调整,需要排序,所以我们需要建一个大堆

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

步骤一:构建 初始堆,将给定的无序序列构成一个大顶堆(一般升序采用大顶堆,降序采用小堆)
步骤二:将栈顶元素和末尾元素进行交换,使得末尾元素最大,然后继续调整堆,将栈顶元素 和末尾元素交换,得到第二大的元素,如此反复的交换重建、交换。

void AjustDownLoop(int arr[], int size, int root)
{
     int parent = root;
     int left;
     int right;
     int maxchild;
     while (parent<size)
     {
          left = parent * 2 + 1;
          right = parent * 2 + 2;
          if (left>=size)//没有右孩子
          {
              return;
          }
          maxchild = left;
          if ((right<size) && (arr[right] > arr[left]))
          {
                   maxchild = right;
          }
          if (arr[parent] > arr[maxchild])
          {
              return;
          }
          swap(arr + parent, arr + maxchild);
          parent = maxchild;
     }
}
void HeapSort(int arr[], int size)//堆排序
{
     int i = (size - 2) / 2;
     for (; i >= 0; i--)
     {
          AjustDownLoop(arr, size, i);//向下调整
     }
     for (i = 0; i < size; i++)
     {
          swap(arr + 0, arr + size - i - 1);//交换 最大的元素和最后一个元素的位置
          AjustDownLoop(arr, size - 1 - i, 0);//减小堆得调整范围
     }
}

选择排序和插入排序对比:

各大排序算法的复杂度对比

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值