(端午特别版)【C语言 - 数据结构】排序(上)

简介: (端午特别版)【C语言 - 数据结构】排序(上)

一、排序的概念及其运用


1.1排序的概念


 排序的严格定义:假设含有 个记录的序列为{r1,r2. ... ... .rn}其相应的别为 ,其相应的关键字分别为{k1, k2, ……kn},需确定1,2,……n 的一种排列p1, p2, ……pn,使其相应的关键字满足 kpl<=kp2<=……<=kpn(非递减或者非递增)关系,即使得序列成为一个按关键字的序列(rp1, rp2,……rpn),这样的操作就称为排序。


所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。


啥是稳定性,稳定性的概念:


 稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。


内部排序和外部排序的概念:


内部排序:数据元素全部放在内存中的排序。


外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序。


对于内排序来说,排序算法的性能主要是受三个方面影响:


1.时间性能:排序是数据处理中经常执行的一种操作,往往属于系统的核心部分,因此排序算法的时间开销是衡量其好坏的最重要的标志。在内排序中,主要进行两种操作:比较和移动 比较指关键字之间的比较,这是要做排序最起码的操作。 移动指记录从一个 位置移动到另一个位置,事实上,移动可以通过改变记录的存储方式来予以避免。


2.辅助空间:评价排序算法的另一个主要标准是执行算法所需要的辅助存储空间。辅助存储空 间是除了存放待排序所占用的存储空间之外,执行算法所需要的其他存储空间。


3.算法的复杂性:注意这里指的是算法本身的复杂度,而不是指算法的时间复杂度。显然算法过于复杂也会影响排序的性能。


1.2常见的排序算法


1669441029572.jpg


二、常见的排序算法及其实现


2.1冒泡排序


我想大家都对冒泡排序不陌生,毕竟这是许多初学者最开始学的一种排序算法,今天让我们剖析一些这个算法吧!

 

1669441054131.jpg


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


冒泡排序的代码:

void BubbleSort(int* a, int n)
{
  for (int i = 0; i < n - 1; i++)
  {
  int exchange = 0;//定义一个变量判断是否发生了交换,若没有交换则直接break出去
  for (int j = 0; j < n - i - 1; j++)
  {
    if (a[j] > a[j + 1])
    {
    exchange = 1;
    Swap(&a[j], &a[j + 1]);
    }
  }
  if (exchange == 0)//说明没有交换过
  {
    break;
  }
  }
}


如果是用C语言写的代码建议大家写一个Swap函数和PrintfArray输出数组的函数


Swap和PrintfArray函数代码:

void Swap(int* pa, int* pb)
{
  int tmp = *pa;
  *pa = *pb;
  *pb = tmp;
}
void PrintfArray(int* a, int n)
{
  int i = 0;
  for (i = 0; i < n; i++)
  {
  printf("%d ", a[i]);
  }
  printf("\n");
}

2.2插入排序


2.2.1插入排序的基本思想


直接插入排序是一种简单的插入排序法,其基本思想是:把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列 。


2.2.2插入排序的操作:


当插入第i(i>=1)个元素时,前面的array[0],array[1],…,array[i-1]已经排好序,此时用array[i]的排序码与 array[i-1],array[i-2],…的排序码顺序进行比较,找到插入位置即将array[i]插入,原来位置上的元素顺序后移即可。


2.2.3直接插入排序的特性总结:


1. 元素集合越接近有序,直接插入排序算法的时间效率越高


2. 时间复杂度:O(N^2)


3. 空间复杂度:O(1),它是一种稳定的排序算法


4. 稳定性:稳定


插入排序代码:

void InsertSort(int* a, int n)
{
  for (int i = 0; i < n - 1; i++)
  {
  //[0, end] end + 1
  int end = i;
  //单趟排序; [0, end] 有序的。 end + 1位置的值,插入进入,保持他依旧有序
  int tmp = a[end + 1];
  while (end >= 0)
  {
    if (tmp < a[end])
    {
    a[end + 1] = a[end];//先交换
    --end;//往前
    }
    else
    {
    break;
    }
    a[end + 1] = tmp;//tmp的值改到了之前的end位置
  }
  }
}


冒泡排序和插入排序对比:


1669441137232.jpg


2.3堆排序


堆排序即利用堆的思想来进行排序,总共分为两个步骤:


1. 建堆 升序:建大堆


          .降序:建小堆


2. 利用堆删除思想来进行排序 建堆和堆删除中都用到了向下调整,因此掌握了向下调整,就可以完成堆排序。


书中的描述:


堆排序 (Heap Sort) 就是利用堆(假设利用大顶堆)进行排序的方法。它的基本思想是, 将待排序的序列构造成一个大根堆。此时,整个序列的最大值就是堆顶的根结点。将它移走(其实就是将其与堆数组的末尾元素交换,此时末尾元素就是最大值) .然后将剩余的n-1个序列重新构造成一个堆,这样就会得到n个元素中的次小值。如此反复执行,便能得到一个有序序列了。


代码示例:(小根堆)

void AdjustDown(HPDataType* a, size_t size, size_t root)
{
       size_t parent = root;
       size_t child = parent * 2 + 1;//默认是左孩子
       while (child < size)
       {
              //1、选出左右孩子中小的那个,而且保证不会越界访问
              if (child + 1 < size && a[child + 1] <a[child])//建大堆时< 改 >
              {
                      ++child;//左孩子变为右孩子
              }
              if (a[child] < a[parent])//建大堆时< 改 >
              {
                      Swap(&a[child], &a[parent]);
                      parent = child;//继续计算
                      child = parent * 2 + 1;//默认还是计算左孩子
              }
              else
              {
                      break;
              }
       }
}
void HeapSort2(int* a, int n)
{
  //向上调整--建堆
  //for (int i = 1; i < n; ++i)
  //{
  //  AdjustUp(a, i);
  //}
  //向下调整--建堆O(N)
  for (int i = (n - 1 - 1) / 2; i >= 0; --i)
  {
  AdjustDown(a, n, i);//为什么向下调整要多传一个参数,因为当child>=size 时说明已经到了边界
  }
  size_t end = n - 1;//n - 1是最后一个数据的下标
  while (end > 0)
  {
  Swap(&a[0], &a[end]);
  AdjustDown(a, end, 0);
  //次大的数到了倒数第二个位置
  --end;
  }
}
int main()
{
//  TestHeap();
  int a[] = { 4 , 2, 7, 8, 5, 1, 0, 6 };
  HeapSort(a, sizeof(a) / sizeof(int));
  for (int i = 0; i < sizeof(a) / sizeof(int); i++)
  {
  printf("%d ", a[i]);
  }
  printf("\n");
  return 0;
}


2.4选择排序


2.4.1基本思想:


每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的 数据元素排完 。


2.4.2直接选择排序:


1、在元素集合array[i]--array[n-1]中选择关键码最大(小)的数据元素


2、若它不是这组元素中的最后一个(第一个)元素,则将它与这组元素中的最后一个(第一个)元素交换


3、在剩余的array[i]--array[n-2](array[i+1]--array[n-1])集合中,重复上述步骤,直到集合剩余1个元素。


2.4.3直接选择排序的特性总结:


1. 直接选择排序思考非常好理解,但是效率不是很好。实际中很少使用


2. 时间复杂度:O(N^2)


3. 空间复杂度:O(1)


4. 稳定性:不稳定


选择排序与堆排序对比:


选择排序在每选出一个数需要遍历N次


堆排序建好堆后只需logN


所以堆排序的时间复杂度是o(n*logN),而选择排序是o(n^2);


选择排序的代码:


思路:每一次选出最大最小值,最大放最后,最小放后面,再left++,right--缩小区间


void SelectSort(int* a, int n)
{
    int left = 0, right = n - 1;
    while (left < right)
    {
        int mini = left, maxi = left;
        for (int i = left + 1; i <= right; i++)
        {
            if (a[i] < a[mini])
            {
                mini = i;
            }
            if (a[i] > a[maxi])
            {
                maxi = i;
            }
        }
        Swap(&a[left], &a[mini]);
        //maxi可能与left重叠
        if (left == maxi)
        {
            maxi = mini;
        }
        Swap(&a[right], &a[maxi]);
        left++;
        right--;
    }
}


2.5希尔排序


希尔排序我们可以简单理解成是插入排序加强版,而且是史诗级加强。


2.5.1希尔排序的原理:


现在,我要讲解的算法叫希尔排序 (Shell Sort ,希尔排序是 DL.Shell 1959年提出来的一种排序算法,在这之前排序算法的时间复杂度基本都是 O(n^2)的,希尔排序算法是突破这个时间复杂度的第一批算法。


原理:


先选定一个整数,把待排序文件中所有记录分成个 组,所有距离为的记录分在同一组内,并对每一组内的记录进行排序。然后,取,重复上述分组和排序的工 作。当到达=1时,所有记录在统一组内排好序。


1669441222317.jpg


2.5.2希尔排序的特性总结:


1. 希尔排序是对直接插入排序的优化。


2. 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就 会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。


3. 希尔排序的时间复杂度不好计算,因为gap的取值方法很多,导致很难去计算,因此在好些树中给出的 希尔排序的时间复杂度都不固定。


希尔排序代码:

void ShellSort(int* a, int n)
{
    //预排序
    int gap = n;
    while (gap > 1)
    {
        gap = gap / 3 + 1;
        for (int i = 0; i < n - gap; i += gap)//为什么是到n - gap因为
        //结束前最后一次执行的下标是n - gap - 1
        {
            int end = i;
            int tmp = a[end + gap];
            while (end >= 0)
            {
                if (tmp < a[end])
                {
                    a[end + gap] = a[end];
                    end -= gap;
                }
                else
                {
                    break;
                }
                a[end + gap] = tmp;
            }
        }
    }
}


三、排序算法复杂度及稳定性分析


3.1算法的空间特征


·需要使用堆栈或者递归实现:快排和归并排序


·常数空间复杂度:堆排序、希尔、插入、冒泡、选择


3.2算法与数据结构的关系


数据不敏感:归并排序、选择排序、堆排序


数据敏感:冒泡、快排、插入、希尔。


3.3算法的分类:


分治:归并和快排


基于插入:希尔、直接插入、选择


基于选择:堆排序、直接选择


3.4算法的稳定性:


稳定算法:归并、插入、冒泡


不稳定:堆排序、快排、希尔、选择


1669441268965.jpg


 数据量为1万时插入、希尔、和冒泡算法的时间:


1669441278031.jpg


 数据量为10万时插入、希尔、和冒泡算法的时间:


1669441286294.jpg


数据量为1000万时算法的时间(只比较堆排序和希尔排序)


 1669441295415.jpg

相关文章
|
22天前
|
算法 数据处理 C语言
C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合
本文深入解析了C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合,旨在帮助读者掌握这一高效的数据处理方法。
34 1
|
1月前
|
存储 算法 搜索推荐
【趣学C语言和数据结构100例】91-95
本文涵盖多个经典算法问题的C语言实现,包括堆排序、归并排序、从长整型变量中提取偶数位数、工人信息排序及无向图是否为树的判断。通过这些问题,读者可以深入了解排序算法、数据处理方法和图论基础知识,提升编程能力和算法理解。
45 4
|
1月前
|
存储 机器学习/深度学习 搜索推荐
【趣学C语言和数据结构100例】86-90
本文介绍并用C语言实现了五种经典排序算法:直接插入排序、折半插入排序、冒泡排序、快速排序和简单选择排序。每种算法都有其特点和适用场景,如直接插入排序适合小规模或基本有序的数据,快速排序则适用于大规模数据集,具有较高的效率。通过学习这些算法,读者可以加深对数据结构和算法设计的理解,提升解决实际问题的能力。
43 4
|
1月前
|
存储 算法 数据处理
【趣学C语言和数据结构100例】81-85
本文介绍了五个经典算法问题及其C语言实现,涵盖图论与树结构的基础知识。包括使用BFS求解单源最短路径、统计有向图中入度或出度为0的点数、统计无向无权图各顶点的度、折半查找及二叉排序树的查找。这些算法不仅理论意义重大,且在实际应用中极为广泛,有助于提升编程能力和数据结构理解。
38 4
|
1月前
|
算法 数据可视化 数据建模
【趣学C语言和数据结构100例】76-80
本文介绍了五种图论算法的C语言实现,涵盖二叉树的层次遍历及广度优先搜索(BFS)和深度优先搜索(DFS)的邻接表与邻接矩阵实现。层次遍历使用队列按层访问二叉树节点;BFS利用队列从源节点逐层遍历图节点,适用于最短路径等问题;DFS通过递归或栈深入图的分支,适合拓扑排序等场景。这些算法是数据结构和算法学习的基础,对提升编程能力和解决实际问题至关重要。
46 4
|
1月前
|
存储 算法 vr&ar
【趣学C语言和数据结构100例】71-75
本文介绍了五个C语言数据结构问题及其实现,涵盖链表与二叉树操作,包括按奇偶分解链表、交换二叉树左右子树、查找节点的双亲节点、计算二叉树深度及求最大关键值。通过递归和遍历等方法,解决了理论与实际应用中的常见问题,有助于提升编程能力和数据结构理解。
37 4
|
1月前
|
存储 算法 C语言
【趣学C语言和数据结构100例】66-70
本书《趣学C语言和数据结构100例》精选了5个典型的数据结构问题及C语言实现,涵盖链表与数组操作,如有序集合的集合运算、有序序列表的合并、数组中两顺序表位置互换、三递增序列公共元素查找及奇偶数重排。通过详细解析与代码示例,帮助读者深入理解数据结构与算法设计的核心思想,提升编程技能。
32 4
|
23天前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
44 5
|
22天前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
52 1
|
1月前
|
存储 机器学习/深度学习 算法
【趣学C语言和数据结构100例】61-65
本文介绍了五个关于C语言和数据结构的经典问题及其实现方法,涵盖查找链表共同后缀、删除重复节点、重新排列链表元素、合并有序链表以及特定条件下的链表排序。每个问题通过具体的算法设计,不仅展示了链表操作的灵活性,还强调了效率优化的重要性。通过这些问题的探讨,读者可以深入理解链表的基本操作和高级应用,提升解决实际问题的能力。
46 4