数据结构——排序(C语言实现)(二)

简介: 数据结构——排序(C语言实现)

冒泡排序

void Swap(int* a, int* b)
{
  int c = *a;
  *a = *b;
  *b = c;
}
void bubbling()
{
  int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
  int n = sizeof(arr) / sizeof(arr[0]);
  int x = 1;
  for (int j = 0; j < n; j++)
  {
    for (int i = 0; i < n - 1 - j; i++)
    {
      if (arr[i] > arr[i + 1])
      {
        Swap(&arr[i], &arr[i + 1]);
        x = 0;
      }
    }
    if (x == 1)//如果x没有被置为0就说明没有可以交换的数据了
    {
      break;
    }
  }
}

这个排序是我最早接触的排序。

时间复杂度为:O(N2)

虽然很慢,但是很容易理解。

快速排序

基本思想:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

快排有三种方法。(原理都是类似的)

hoare法

这个版本的方法是先选择一个key值,一般是在第一个或者是最后一个位置,然后两个变量从头和尾向中间走,相遇就会停下。

例:

这里key我选的是数组中第一个值,R找比key小的,L找比key大的,如果R找到的值比key找到的小就与L位置进行交换(但是key值只在R与L相遇才会换位置),就像这样,假如R先走,就先和key比较:

这里R走了两步,因为8比6大,10比6大,现在R的位置是4,比6小,停下,这里不进行交换,然后L走,L到7的位置停下,因为7比6大,然后R和L的位置进行交换。

然后R走一步到3的位置停下,L走一步,发现没有比key大,再走一步与R相遇,这时将R与L相遇的位置与key值的位置进行交换。

那么怎么保证L与R相遇的位置的值一定小于key呢?答案是R先走。

R停下,说明撞到了L,L虽然是找比key大的,但是在R走之前L与R位置的值一定互换了,就算L一直没动,就说明所有的值都比key大。

L停下,撞到了R,R是找小,撞到R,R的位置一定是小于key的数,如果R没动,说明所有数都比key小。

同理,如果key在右边第一个值,L先走。

排序完成之后我们发现6的前面和后面正好分成了两个区间,6的位置也是不用在进行移动的,这就是但趟排序,然后再用将这组数的前后区间进行相同方法的排序就可以了,直到剩下最后一个数为止。

以此类推,其实就是一个递归二叉树的原理。

代码实现:

#include <stdio.h>
void Swap(int* a,int* b)
{
  int c = *a;
  *a = *b;
  *b = c;
}
int single_row(int* a, int start, int end)//单趟排序
{
  int key = start;//让key变成头部位置
  while (start < end)
  {
    while (a[end] >= a[key] && end > start)//如果这里不是>=key可能会会产生死循环
    {
      end--;
    }
    while (a[start] <= a[key] && end > start)
    {
      start++;
    }
    if (start < end)//没有相遇就交换
    {
      Swap(&a[start], &a[end]);
    }
  }
  Swap(&a[key], &a[end]);//当R与L相遇交换key和相遇位置的数据
  return end;//返回相遇的位置
}
void Quicksort(int* a, int start, int end)//调用一次这个函数就等于使用一次单排的逻辑
{
  if (start >= end)//如果剩余一个数或者是没有数据就不用排序了
  {
    return;
  }
  int key = single_row(a, start, end);//单排,返回值是已经排序好的数值的位置
  Quicksort(a, start, key - 1);//左区间
  Quicksort(a, key + 1, end);//右区间
}
int main()
{
  int arr[] = { 6,2,7,1,3,4,10,8 };
  int n = sizeof(arr) / sizeof(arr[0]);
  Quicksort(arr, 0, n - 1);
  for (int i = 0; i < n; i++)
  {
    printf("%d ", arr[i]);
  }
  return 0;
}

这棵二叉树的深度是logN,每层单排的时间复杂度是O(N).

总体时间复杂度是O(N*logN), 但是这种情况只发生在相遇的地方是在数组中间左右位置的地方。

那么如果每次相遇的地方总是在头或者是尾会怎么样呢?

红色是key的位置,左边树一直都是空的,也就是说这棵树的深度是N,时间复杂度就是O(N2),这样还能能被称为快排吗?不仅仅如此,还会造成栈溢出的情况。

所以我们要优化一下。

快排的优化

有两个优化的地方:

1.key值的调整

2.避免给二叉树的后三层排序

key值的调整取中间数明显是最好的,不过数组中想取中间数很困难,不能让key每次都取数组中间的数,因为key随意位移单排的整体代码都会被更改,如果想让key的数更换直接让中间的数与开头的数交换就可以了,但是如果换过来的数正好是最小或者是最大的数呢?那么就又会发生上面的事情,所以要三数取中法,选择开头的数和中间的数,末尾的数,然后比大小,选择中间大的数。

避免二叉树的后三层是因为最后一层的结点占了整个二叉树的50%,倒数第二层占了25%,倒数第三层占了12.5%,如果在倒数第四层的地方用直接插入排序会更好,因为每次排序都会接近更有序,所以用直接插入排序更划算。

因为单排要等最后一个数才会停止,所以可以推理出倒数第四层为8个数。

代码实现

#include <stdio.h>
void sort(int* arr, int n)
{
  int i = 0;//控制end
  for (i = 0; i < n - 1; i++)
  {
    int end = i;//这个是两个数比较中的第一个数
    int s = arr[end + 1];//保留的是两个数的后面一个数
    while (end >= 0)
    {
      if (arr[end] > s)//比较两个数的大小
      {
        arr[end + 1] = arr[end];//如果不是从大到小就重新排序
        end--;
      }
      else
      {
        break;
      }
    }
    arr[end + 1] = s;//如果顺序没错就将储存起来的数据放到指定位置,这么写的好处还有一个是end最坏的情况会到-1
  }
}
void Swap(int* a, int* b)
{
  int c = *a;
  *a = *b;
  *b = c;
}
int middle(int* a, int start, int end)//取中间数
{
  int mid = (start + end) / 2;
  if (a[start] < a[mid])
  {
    if (a[start] > a[end])//mid最大,end最小
    {
      return start;
    }
    else if (a[end] > a[mid])//end最大,start最小
    {
      return mid;
    }
    else//mid最大,start最小
    {
      return end;
    }
  }
  else//a[start] >= a[mid]
  {
    if (a[end] > a[start])//end最大,mid最小
    {
      return start;
    }
    else if(a[mid] > a[end])//start最大,end最小
    {
      return mid;
    }
    else//start最大,mid最小
    {
      return end;
    }
  }
}
int PartSort(int* a, int start, int end)//单趟排序
{
  int key = middle(a, start, end);
  Swap(&a[key], &a[start]);//交换中间数和第一个数的位置
  while (start < end)
  {
    while (a[end] >= a[key] && end > start)//如果这里不是>=key可能会会产生死循环
    {
      end--;
    }
    while (a[start] <= a[key] && end > start)
    {
      start++;
    }
    if (start < end)//没有相遇就交换
    {
      Swap(&a[start], &a[end]);
    }
  }
  Swap(&a[key], &a[end]);//当R与L相遇交换key和相遇位置的数据
  return end;//返回相遇的位置
}
void Quicksort(int* a, int start, int end)//调用一次这个函数就等于使用一次单排的逻辑
{
  if (start >= end)//如果剩余一个数或者是没有数据就不用排序了
  {
    return;
  }
  if (end - start <= 8)
  {
    sort(a + start, end - start + 1);//第一个参数因为左右区间的问题,第二个参数要加一的原因是要跳过已经排序号位置的数
  }
  else
  {
    int key = PartSort(a, start, end);//单排,返回值是已经排序好的数值的位置
    Quicksort(a, start, key - 1);//左区间
    Quicksort(a, key + 1, end);//右区间
  }
}
int main()
{
  int arr[] = { 6,2,7,1,3,4,10,8 };
  int n = sizeof(arr) / sizeof(arr[0]);
  Quicksort(arr, 0, n - 1);
  for (int i = 0; i < n; i++)
  {
    printf("%d ", arr[i]);
  }
  return 0;
}

相关文章
|
12月前
|
算法 数据处理 C语言
C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合
本文深入解析了C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合,旨在帮助读者掌握这一高效的数据处理方法。
501 1
|
12月前
|
存储 算法 搜索推荐
【趣学C语言和数据结构100例】91-95
本文涵盖多个经典算法问题的C语言实现,包括堆排序、归并排序、从长整型变量中提取偶数位数、工人信息排序及无向图是否为树的判断。通过这些问题,读者可以深入了解排序算法、数据处理方法和图论基础知识,提升编程能力和算法理解。
169 4
|
12月前
|
存储 机器学习/深度学习 搜索推荐
【趣学C语言和数据结构100例】86-90
本文介绍并用C语言实现了五种经典排序算法:直接插入排序、折半插入排序、冒泡排序、快速排序和简单选择排序。每种算法都有其特点和适用场景,如直接插入排序适合小规模或基本有序的数据,快速排序则适用于大规模数据集,具有较高的效率。通过学习这些算法,读者可以加深对数据结构和算法设计的理解,提升解决实际问题的能力。
149 4
|
9月前
|
存储 机器学习/深度学习 算法
C 408—《数据结构》图、查找、排序专题考点(含解析)
408考研——《数据结构》图,查找和排序专题考点选择题汇总(含解析)。
505 29
|
9月前
|
定位技术 C语言
c语言及数据结构实现简单贪吃蛇小游戏
c语言及数据结构实现简单贪吃蛇小游戏
|
10月前
|
搜索推荐 C语言
数据结构(C语言)之对归并排序的介绍与理解
归并排序是一种基于分治策略的排序算法,通过递归将数组不断分割为子数组,直到每个子数组仅剩一个元素,再逐步合并这些有序的子数组以得到最终的有序数组。递归版本中,每次分割区间为[left, mid]和[mid+1, right],确保每两个区间内数据有序后进行合并。非递归版本则通过逐步增加gap值(初始为1),先对单个元素排序,再逐步扩大到更大的区间进行合并,直至整个数组有序。归并排序的时间复杂度为O(n*logn),空间复杂度为O(n),且具有稳定性,适用于普通排序及大文件排序场景。
|
10月前
|
存储 人工智能 算法
【C++数据结构——内排序】二路归并排序(头歌实践教学平台习题)【合集】
本关任务是实现二路归并算法,即将两个有序数组合并为一个有序数组。主要内容包括: - **任务描述**:实现二路归并算法。 - **相关知识**: - 二路归并算法的基本概念。 - 算法步骤:通过比较两个有序数组的元素,依次将较小的元素放入新数组中。 - 代码示例(以 C++ 为例)。 - 时间复杂度为 O(m+n),空间复杂度为 O(m+n)。 - **测试说明**:平台会对你编写的代码进行测试,提供输入和输出示例。 - **通关代码**:提供了完整的 C++ 实现代码。 - **测试结果**:展示代码运行后的排序结果。 开始你的任务吧,祝你成功!
296 10
|
10月前
|
搜索推荐 算法 数据处理
【C++数据结构——内排序】希尔排序(头歌实践教学平台习题)【合集】
本文介绍了希尔排序算法的实现及相关知识。主要内容包括: - **任务描述**:实现希尔排序算法。 - **相关知识**: - 排序算法基础概念,如稳定性。 - 插入排序的基本思想和步骤。 - 间隔序列(增量序列)的概念及其在希尔排序中的应用。 - 算法的时间复杂度和空间复杂度分析。 - 代码实现技巧,如循环嵌套和索引计算。 - **测试说明**:提供了测试输入和输出示例,帮助验证代码正确性。 - **我的通关代码**:给出了完整的C++代码实现。 - **测试结果**:展示了代码运行的测试结果。 通过这些内容,读者可以全面了解希尔排序的原理和实现方法。
214 10
|
10月前
|
搜索推荐 C++
【C++数据结构——内排序】快速排序(头歌实践教学平台习题)【合集】
快速排序是一种高效的排序算法,基于分治策略。它的主要思想是通过选择一个基准元素(pivot),将数组划分成两部分。一部分的元素都小于等于基准元素,另一部分的元素都大于等于基准元素。然后对这两部分分别进行排序,最终使整个数组有序。(第一行是元素个数,第二行是待排序的原始关键字数据。本关任务:实现快速排序算法。开始你的任务吧,祝你成功!
245 7
|
10月前
|
存储 算法 安全
【C语言程序设计——选择结构程序设计】按从小到大排序三个数(头歌实践教学平台习题)【合集】
本任务要求从键盘输入三个数,并按从小到大的顺序排序后输出。主要内容包括: - **任务描述**:实现三个数的排序并输出。 - **编程要求**:根据提示在编辑器中补充代码。 - **相关知识**: - 选择结构(if、if-else、switch) - 主要语句类型(条件语句) - 比较操作与交换操作 - **测试说明**:提供两组测试数据及预期输出。 - **通关代码**:完整代码示例。 - **测试结果**:展示测试通过的结果。 通过本任务,你将掌握基本的选择结构和排序算法的应用。祝你成功!
163 4

热门文章

最新文章