十大经典排序算法(C语言实现)(一)

简介: 十大经典排序算法(C语言实现)(一)

前言


本文代码链接:十大经典排序算法

提取码:2ok3

排序算法是《数据结构与算法》的重要组成部分,在项目实践中,很多时候都需要用到排序算法,而常见的经典排序算法也是很多公司程序员面试的重点。十大经典排序算法如下图所示。

时间复杂度空间复杂度是衡量一个算法性能好坏的重要指标。而对于排序算法而言,稳定性也是重要指标之一。

教材上给了非常严谨且抽象的定义。

假设ki=kj(1<=i<=n,1<=j<=n,i≠j),且在排序前的序列中ri领先于rj,若果排序后ri仍然领先于rj,则称所用的排序方法是稳定的;反之,若可能使得排序后的序列中rj领先于ri,则称所用的排序方法是不稳定的。

通俗地说,有时候在原序列中两个数值是相等的,如果排序后可以保证原来的相对位置不变,则称该算法是稳定的,若不能保证,则算法是不稳定的。

关于算法的时间复杂度空间复杂度稳定性,见下面表格。

1 冒泡排序

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

因较小的数字如同气泡一般慢慢浮到上面,故而得名冒泡排序

1.1 最简单的排序算法

代码如下:

#include <stdio.h>
#define MAXSIZE 10000
typedef struct
{
  int r[MAXSIZE + 1];
  int length;
}sqList;
void swap(sqList* L, int i, int j)
{
  int temp = L->r[i];
  L->r[i] = L->r[j];
  L->r[j] = temp;
}
void Bubblesort0(sqList* L)
{
  for (int i = 0; i < L->length; i++)
  {
    for (int j = i; j < L->length; j++)
    {
      if (L->r[i] > L->r[j])
        swap(L, i, j);
    }
  }
}
int main()
{
  sqList test = {{9,1,5,8,3,7,4,6,2}, 9};
  Bubblesort0(&test);
  for (int i = 0; i < test.length; i++)
    printf("%d\t", test.r[i]);
 return 0;
}

以上是最简单的冒泡排序算法的实现,每次循环保证得到该范围内的最小值,排在前面,从而完成排序。如图所示:

然而还有改进的空间。是否可在每次循环的时候,比较更多的关键字呢?于是有了改进版的冒泡排序。

1.2 冒泡排序算法

通过以上的改进思路,可以得到一下的代码。

#include <stdio.h>
#define MAXSIZE 10000
typedef struct
{
  int r[MAXSIZE + 1];
  int length;
}sqList;
void swap(sqList* L, int i, int j)
{
  int temp = L->r[i];
  L->r[i] = L->r[j];
  L->r[j] = temp;
}
void Bubblesort1(sqList* L)
{
  for (int i = 0; i < L->length; i++)
  {
    for (int j = L->length - 2; j >= i; j--)
    {
      if (L->r[j] > L->r[j+1])
        swap(L, j, j + 1);
    }
  }
}
int main()
{
  sqList test = {{9,1,5,8,3,7,4,6,2}, 9};
  Bubblesort1(&test);
  for (int i = 0; i < test.length; i++)
    printf("%d\t", test.r[i]);
 return 0;
}

通过以上的改进,在每次循环时,可以比较相邻的关键字,从而变得更加高效。如下图所示:

从上图可以看出,一次循环就可以比较更多的数值。所以是个更好的方法。

1.3 冒泡排序算法优化

上面的例子虽然是正宗的冒泡排序算法,但是仍然有改进的空间,如果能在需要排序的数组有序的时候停止循环,肯定会更加高效,于是有了下面的代码。

#include <stdio.h>
#define MAXSIZE 10000
#define TRUE 1
#define FALSE 0
typedef struct
{
  int r[MAXSIZE + 1];
  int length;
}sqList;
void swap(sqList* L, int i, int j)
{
  int temp = L->r[i];
  L->r[i] = L->r[j];
  L->r[j] = temp;
}
void Bubblesort2(sqList* L)
{
  short flag = TRUE;
  for (int i = 0; i < L->length && flag; i++)
  {
    flag = FALSE;
    for (int j = L->length - 2; j >= i; j--)
    {
      if (L->r[j] > L->r[j + 1])
      {
        swap(L, j, j + 1);
        flag = TRUE;
      }
    }
  }
}
int main()
{
  sqList test = {{9,1,5,8,3,7,4,6,2}, 9};
  Bubblesort2(&test);
  for (int i = 0; i < test.length; i++)
    printf("%d\t", test.r[i]);
 return 0;
}

这样一来就避免了无意义的循环,如果上次发现算法已经完成了排序,程序就不会进入循环,从而提前结束运行,完成排序任务。

1.4 冒泡排序算法总结

冒泡算法是最常用的算法之一,也是最简单的排序算法之一,但却不是最高效的,以下将介绍其他几种排序算法。

2 选择排序

选择排序的方法也非常好理解,但它并不像冒泡排序一样,遇到顺序不合适的就直接调换位置,而是记录下最小关键字的位置,待循环完毕后再将其与此次循环的第一个关键字的位置做调换,从而保证了每次循环都可以得到该范围内的最小值,故而得名选择排序

2.1 选择排序的代码实现

具体的实现代码如下。

#include <stdio.h>
#define MAXSIZE 10000
#define TRUE 1
#define FALSE 0
typedef struct
{
  int r[MAXSIZE + 1];
  int length;
}sqList;
void swap(sqList* L, int i, int j)
{
  int temp = L->r[i];
  L->r[i] = L->r[j];
  L->r[j] = temp;
}
void SelectSort(sqList *L)
{
  int i, j, min;
  for (i = 0; i < L->length; i++)
  {
    min = i;
    for (j = i + 1; j < L->length; j++)
    {
      if (L->r[min] > L->r[j])
        min = j;
    }
    if (i != min)
      swap(L, i, min);
  }
}
int main()
{
  sqList test = {{9,1,5,8,3,7,4,6,2}, 9};
  SelectSort(&test);
  for (int i = 0; i < test.length; i++)
  printf("%d\t", test.r[i]);
  return 0;
}

通俗地理解,就是不轻易“出手”,外部循环一次,最多调换一次,所以相比冒泡排序稍微高效一些。选择排序的过程如下图所示:

2.2 选择排序算法总结

虽然选择排序比冒泡排序高效一些,但仍然是n2的时间复杂度。

3 插入排序

插入排序又叫直接插入排序或者简单插入排序,这样称呼其实是为了与希尔排序进行区分,其实是同一种排序算法。

所谓插入排序,是将一个记录插入到已经排好序的有序表中,从而得到一个新的,记录数值增1的有序表。

3.1 插入排序的代码实现

插入排序的具体代码如下所示。

注意在插入排序中有个辅助空间,所以数组的第一个元素值为0,排序后的值无效。

#include <stdio.h>
#define MAXSIZE 10000
#define TRUE 1
#define FALSE 0
typedef struct
{
  int r[MAXSIZE + 1];
  int length;
}sqList;
void InsertSort(sqList *L)
{
  int i, j;
  for (i = 2; i < L->length; i++)
  {
    if (L->r[i] < L->r[i - 1])
    {
      L->r[0] = L->r[i];
      for (j = i - 1; L->r[j] > L->r[0]; j--)
        L->r[j + 1] = L->r[j];
      L->r[j + 1] = L->r[0];
    }
  }
}
int main()
{
  sqList test = {{0,9,1,5,8,3,7,4,6,2}, 10};
  InsertSort(&test);
  for (int i = 0; i < test.length; i++)
  printf("%d\t", test.r[i]);
  return 0;
}

插入排序的算法思想可简单理解为,首先确定需要排序的关键字,然后再放到整个数组的第一个位置,再将其放回原数组中,放回的时候进行排序,但只保证该位置及其前面关键字的相对位置没有问题。

如图所示:

从上图中可以看出,所谓的插入排序,是两两进行比较,若发现顺序相反,则将其放入辅助空间中,然后调整其他元素的位置,找到合适的位置插入,从而完成此次排序。

3.2 插入排序算法总结

与冒泡排序和选择排序算法不同的是,插入排序算法需要一个额外的空间来存储数据,但其性能比前两者要稍微好一些,平均比较和移动的次数约为(n2)/4。

相关文章
|
30天前
|
存储 算法 C语言
"揭秘C语言中的王者之树——红黑树:一场数据结构与算法的华丽舞蹈,让你的程序效率飙升,直击性能巅峰!"
【8月更文挑战第20天】红黑树是自平衡二叉查找树,通过旋转和重着色保持平衡,确保高效执行插入、删除和查找操作,时间复杂度为O(log n)。本文介绍红黑树的基本属性、存储结构及其C语言实现。红黑树遵循五项基本规则以保持平衡状态。在C语言中,节点包含数据、颜色、父节点和子节点指针。文章提供了一个示例代码框架,用于创建节点、插入节点并执行必要的修复操作以维护红黑树的特性。
47 1
|
3月前
|
存储 算法 C语言
二分查找算法的概念、原理、效率以及使用C语言循环和数组的简单实现
二分查找算法的概念、原理、效率以及使用C语言循环和数组的简单实现
|
3月前
|
算法 C语言
C语言----判断n是否是2的次方数,利用到按位与&,算法n&(n-1)
C语言----判断n是否是2的次方数,利用到按位与&,算法n&(n-1)
|
3月前
|
机器学习/深度学习 算法 C语言
详细介绍递归算法在 C 语言中的应用,包括递归的基本概念、特点、实现方法以及实际应用案例
【6月更文挑战第15天】递归算法在C语言中是强大力量的体现,通过函数调用自身解决复杂问题。递归涉及基本概念如自调用、终止条件及栈空间管理。在C中实现递归需定义递归函数,分解问题并设定停止条件。阶乘和斐波那契数列是经典应用示例,展示了递归的优雅与效率。然而,递归可能导致栈溢出,需注意优化。学习递归深化了对“分而治之”策略的理解。**
55 7
|
3月前
|
算法 Java C语言
Java中的算法与C语言中的函数
Java中的算法与C语言中的函数
32 2
|
3月前
|
存储 算法 搜索推荐
【数据结构和算法】--- 基于c语言排序算法的实现(2)
【数据结构和算法】--- 基于c语言排序算法的实现(2)
29 0
|
3月前
|
搜索推荐 算法 C语言
【数据结构和算法】--- 基于c语言排序算法的实现(1)
【数据结构和算法】--- 基于c语言排序算法的实现(1)
34 0
|
4月前
|
算法 C语言 人工智能
|
4月前
|
算法 编译器 API
C语言易混淆、简单算法、结构体题目练习、常见关键字总结-1
C语言易混淆、简单算法、结构体题目练习、常见关键字总结
|
3月前
|
算法 C语言
数据结构和算法——归并排序(有序子列的归并、递归算法、非递归算法、思路图解、C语言代码)
数据结构和算法——归并排序(有序子列的归并、递归算法、非递归算法、思路图解、C语言代码)
21 0