内排序C语言

目录

1.排序基本概念

01关键词

02什么是排序

03排序的稳定性

04内排序和外排序

05算法的性能

06排序数据组织

2.插入排序

01直接插入排序

02折半插入排序

03希尔排序

3.交换排序

01冒泡排序

02快速排序 

4.选择排序

01简单选择排序

02堆排序

5.归并排序

6.基数排序

7.各种排序比较


1.排序基本概念

01关键词

在排序算法中,关键词是指用于比较和排序的数据元素。在排序过程中,算法根据关键词的大小关系来决定元素的排列顺序。具体来说,对于数字类型的数据,通常使用数字本身作为关键词进行比较排序;而对于字符串类型的数据,则可以使用字典序或者长度等属性作为关键词进行排序。在排序算法中,关键词的选择很关键,它直接影响排序的效率和结果。因此,在实际应用中,需要根据具体场景来选择合适的关键词进行排序。

下面以数字排序为例,说明关键词的概念和作用。

假设有以下一组数字需要进行升序排序:3,9,1,8,5

其中,数字本身就是关键词,按照数字大小进行排序即可。我们可以使用冒泡排序算法来完成排序过程。

第一趟排序:

比较3和9,3小于9,不需要交换位置; 比较9和1,9大于1,交换位置,变成3,1,9,8,5; 比较9和8,9大于8,交换位置,变成3,1,8,9,5; 比较9和5,9大于5,交换位置,变成3,1,8,5,9;

第一趟结束后,最大的数字9已经排到了最后,接下来再从3,1,8,5四个数字中继续找出最大值,放到倒数第二个位置。这时我们发现每次比较时都需要对数字进行大小判断,因此“数字大小”就是这里的关键词。

02什么是排序

排序是将一组数据按照特定的规则或算法,使得数据按照指定的顺序排列的过程。常见的排序方式包括冒泡排序、插入排序、选择排序、快速排序、归并排序等。排序可以用于对数据进行查找、分析和统计等操作。

03排序的稳定性

排序的稳定性指的是对于具有相同关键字的元素,在排序前后它们的相对位置是否发生了改变。若排序前和排序后,相等的数的相对位置不变,则称该排序算法是稳定的;否则称为不稳定的。

例如,有一组数据 {3, 2, 3, 1},如果使用非稳定排序算法对其进行排序,可能得到 {1, 2, 3, 3} 的结果,这时候第一个 3 和第二个 3 的相对位置发生了改变。而如果使用稳定排序算法,则可以保证在排序后第一个 3 在第二个 3 的前面,即 {2, 3, 3, 1}。

稳定性对于某些应用场景非常重要,比如对于基于关键字的快速查找、去重等操作,需要保持元素在数组中的相对位置不变。因此,在选择排序算法时,需要根据实际需求来选择是否需要考虑排序的稳定性。

04内排序和外排序

内排序:在排序过程中,若整个表都是放在内存中处理,排序时不涉及数据的内、外存交换,则称之为内排序。它的运行时间取决于数据本身的特征,空间复杂度是O(1),因此其执行效率比较高,但是对于处理大量数据时,会有内存不足的问题。

外排序:若排序过程中要进行数据的内、外存交换,则称之为外排序。它的运行时间主要受限于磁盘I/O速度,空间复杂度取决于分块大小和缓存区大小。它适用于处理大量数据的情况,但相对于内排序而言,它的执行效率会降低。

05算法的性能

算法的性能主要由时间复杂度和空间复杂度两个方面来衡量。

时间复杂度:表示算法执行所需要的时间,通常用“大O符号”来表示。它是根据算法中基本操作的执行次数来计算的。时间复杂度越小,算法执行效率越高。

空间复杂度:表示算法执行所需要的内存空间大小。通常也用“大O符号”来表示。空间复杂度越小,算法所占用的内存空间越少。

除了时间复杂度和空间复杂度之外,还有其他因素会影响算法的性能,如硬件设备、编程语言、编译器、优化技术等。

因此,在实际应用中,需要综合考虑算法的时间复杂度、空间复杂度以及其他因素,来选择最优算法。

时间效率——排序速度(即排序所花费的全部比较次数)。

空间效率——占内存辅助空间的大小。

稳定性——若两个记录A和B的关键字值相等,若排序后A、B的先后次序保持不变,则称这种排序算法是稳定的。

06排序数据组织

typedef int KeyType;  //定义关键字类型为int
typedef struct        //元素类型
{                     
  KeyType key;        //关键字项
}RecType              //排序元素的类型

2.插入排序

主要的插入排序方法:

(1)直接插入排序 (2)折半插入排序 (3)希尔排序

01直接插入排序

1.排序思路:

直接插入排序是一种简单的排序算法,其基本思想是将待排序的序列分为两部分:有序区和无序区。初始时,有序区只包含一个元素,即序列的第一个元素,而无序区则包含除第一个元素以外的所有元素。排序过程中,每次从无序区取出第一个元素,将它插入到有序区的合适位置上,直到无序区为空,排序完成。

具体的实现步骤如下:

  1. 从第二个元素开始遍历待排序序列。

  2. 将当前元素存储在临时变量中,即将该元素从无序区中移出。

  3. 从后往前遍历有序区,如果有序区中的某个元素大于当前元素,则将该元素向后移动一个位置,直到找到第一个小于等于当前元素的位置。

  4. 将临时变量即当前元素插入到有序区中该位置后面。

  5. 重复执行2-4步,直到无序区为空,排序完成。

2. 直接插入排序的算法如下:

void InsertSort(RecType R[],int n) 
{  int i, j;   RecType tmp;
   for (i=1;i<n;i++) 
   {  if (R[i].key<R[i-1].key]) //反序时
      {  tmp=R[i];
	  j=i-1; 
         do			  //找R[i]的插入位置
         {  R[j+1]=R[j];   	  //将关键字大于R[i].key的元素后移
            j--;
         }  while  (j>=0 && R[j].key>tmp.key)
         R[j+1]=tmp;      	  //在j+1处插入R[i]
      }
   }
}

3.算法分析

直接插入排序的时间复杂度为O(n^2),因为在最坏情况下,每个元素都需要与已排序序列中的每个元素比较,即需要进行n-1次比较。同时,在每一轮比较中,需要将已排序序列中后面的元素依次向后移动,最坏情况下需要移动n-1次。因此,总的比较次数和移动次数都是n(n-1)/2,即时间复杂度为O(n^2)。

直接插入排序的空间复杂度为O(1),因为排序过程中只需要使用常量级别的额外空间,即只需要存储i,j,temp。不需要创建额外的数组或其他数据结构,所以空间复杂度为O(1)。

02折半插入排序

1.排序思路

折半插入排序是一种改进过的插入排序算法,它与直接插入排序的区别在于找插入位置时采用二分查找的方式,从而减少比较次数。具体描述如下:

  1. 将待排序序列第一个元素看作已排序序列,后面的元素看作未排序序列。

  2. 从未排序序列中取出一个元素,利用二分查找,在已排序序列中找到插入位置。

  3. 插入该元素,并将已排序序列中后面的元素依次向后移动,腾出插入位置。

  4. 重复执行步骤2和3,直到未排序序列中没有元素

2.折半插入排序算法如下:

void binaryInsertionSort(int arr[], int n)
 {
    int i, j, left, right, mid;
    int key;
    
    for (i = 1; i < n; i++) {
        key = arr[i];
        left = 0;
        right = i - 1;
        
        // 二分查找插入位置
        while (left <= right) {
            mid = (left + right) / 2;
            if (key < arr[mid]) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
        
        // 将元素插入到正确位置
        for (j = i - 1; j >= left; j--) {
            arr[j+1] = arr[j];
        }
        arr[left] = key;
    }
}




3.算法分析

半插入排序是一个在普通插入排序的基础上进行了优化的算法。它通过使用二分查找确定元素的正确位置,可以减少比较操作的次数,从而提高效率。

时间复杂度

折半插入排序的时间复杂度为O(n^2)。虽然二分查找能够减少比较操作的次数,但是每次插入元素时,仍需要将大于该元素的所有元素后移一个位置,这个操作需要O(n)的时间复杂度。因此,总的时间复杂度仍为O(n^2)。

空间复杂度

折半插入排序的空间复杂度为O(1),因为只需要使用常数级别的额外空间来存储一些临时变量。

稳定性

折半插入排序是一种稳定的排序算法。如果数组中存在相同的元素,在排序后,它们的相对顺序不会改变。

适用性

折半插入排序适用于数据规模较小的情况下,比如10万以下的数据量。当数据规模较大时,其效率很低,并且在最坏情况下,时间复杂度会退化到O(n^2)。

03希尔排序

1.排序思路:

希尔排序是一种改进的插入排序算法,也称为“缩小增量排序”。它通过将待排序数组分成多个子序列进行插入排序,从而达到提高效率的目的。希尔排序的基本思路如下:

  1. 选择一个增量序列,通常取n/2、n/4、n/8...直到1。

  2. 根据选定的增量,将待排序数组分成若干个子序列,分别对每个子序列进行插入排序。

  3. 不断缩小增量,重复步骤2,直到增量为1时,完成最后一次插入排序,得到排好序的数组。

2.希尔排序算法如下:

void ShellSort(RecType R[],int n)
{  int i, j, d;
   RecType tmp;
   d=n/2;	  	//增量置初值
   while (d>0)
   {  for (i=d;i<n;i++)
      {   //对相隔d位置的元素组直接插入排序
         tmp=R[i];
         j=i-d;
	  while (j>=0 && tmp.key<R[j].key)
	  {  R[j+d]=R[j];
	     j=j-d;
	  }
	  R[j+d]=tmp;
      }
      d=d/2;	 	//减小增量
   }
}

 3.算法分析

希尔排序的时间复杂度取决于其使用的增量序列。最坏情况下,当增量序列为1时,与插入排序相同,时间复杂度为O(n^2);而在最好情况下,当增量序列为h[k]=3*h[k-1]+1时,时间复杂度可以达到O(n^(3/2))。平均时间复杂度约为O(nlogn)。

空间复杂度:

希尔排序是原地排序算法,不需要额外的存储空间,因此空间复杂度为O(1)。

3.交换排序

常见的交换排序方法:

(1)冒泡排序(或起泡排序)

(2)快速排序

01冒泡排序

1.排序思路

冒泡排序是一种简单的排序算法,它重复地遍历要排序的列表,每次比较相邻的两个元素,如果它们的顺序错误就交换它们。这样每一趟都会将最大(或最小)的元素“冒泡”到最后面,直到所有元素都排好序为止。

具体实现步骤如下:

  1. 从第一个元素开始,比较相邻的两个元素,如果前一个元素大于后一个元素,就交换它们的位置。

  2. 继续比较下一组相邻的元素,直到比较到最后一对元素。

  3. 重复以上步骤,每次遍历都能确定一个最大(或最小)的元素被“冒泡”到了最后面。

  4. 在每次遍历中,可以减少比较的次数,因为每次遍历都会把一个元素排好序,所以下一次遍历时只需要比较前面未排序的元素即可。

  5. 最终,当所有元素都排好序后,排序完成。

 2.算法实现如下:

void BubbleSort(RecType R[],int n)
{  int i,j;  RecType temp;
   for (i=0;i<n-1;i++) 
   {
      for (j=n-1;j>i;j--)  		//比较找本趟最小关键字的元素
         if (R[j].key<R[j-1].key)	//相邻两个元素反序时   
	     swap(R[j],R[j-1]);    	//将R[j]和R[j-1]两个元素交换
   }
} 

我们知道一旦某一趟比较时不出现元素交换,说明已排好序了,就可以结束本算法。比如(2,1,3,4,5,6)一旦1与2交换后就已经排序好了,而前面的算法仍然要执行好几趟,当它没有交换时说明已经排序好了,因此我们可以从此入手。设置一个标志exchange,有交换时令它为1,无交换令它为0,如果一旦exchange等于0便可以结束算法。代码实现如下:

void BubbleSort(RecType R[],int n)
{  int i,j;  int exchange;  RecType temp;
   for (i=0;i<n-1;i++) 
   {
       exchange=0;
       for (j=n-1;j>i;j--)  
          if (R[j].key<R[j-1].key)  	
          {  swap(R[j],R[j-1]);
             exchange=1;
	   }
       if (exchange==0) return;  //中途结束算法
   }
}

3.算法分析

时间复杂度:

  • 最好情况下,即已经排好序,只需要比较 n-1 次,时间复杂度为 O(n)。
  • 最坏情况下,即需要完全排序,需要进行 n*(n-1)/2 次比较,时间复杂度为 O(n^2)。
  • 平均情况下,时间复杂度也是 O(n^2)。

空间复杂度:

  • 冒泡排序是一种原地排序算法,不需要额外的空间存储数据,所以空间复杂度为 O(1)。

02快速排序 

1.排序思路

快速排序是一种分治思想的排序算法,其基本思路如下:

  1. 选取一个基准值,一般选择待排序序列中的第一个元素或者随机选择。
  2. 将序列中小于等于基准值的元素移动到基准值的左侧,大于基准值的元素移动到右侧,形成两个子序列。该过程称为分区。
  3. 对左右子序列递归地重复步骤 1 和 2,直到每个子序列只剩下一个元素或者为空。

具体实现时,一般采用双指针法来进行分区操作。首先将基准 赋值给left指针对应的元素,然后 right 指针从右往左扫描,找到第一个小于等于 pivot 的元素;接着 left 指针从左往右扫描,找到第一个大于 pivot 的元素;交换这两个元素。重复这个过程,直到 left 指针和 right 指针相遇,将 pivot 放回它最终的位置,即左子序列的最后一个元素或者右子序列的第一个元素。

 

假设有一个数组 arr = [5, 1, 9, 3, 7, 6]

快速排序的步骤如下:

  1. 选取基准值(pivot),一般是数组的第一个数,即 pivot = 5
  2. 定义两个指针 i 和 j,分别指向数组的首尾元素,i = 0,j = len(arr) - 1
  3. 从后往前扫描数组,找到第一个小于等于 pivot 的数,将其与 pivot 交换位置,即 swap(arr[i], arr[j]),此时数组变为 [5, 1, 3, 9, 7, 6],j 减一,即 j = j -1
  4. 从前往后扫描数组,找到第一个大于 pivot 的数,将其与 pivot 交换位置,即 swap(arr[i], arr[j]),此时数组变为 [3, 1, 5, 9, 7, 6],i 加一,即 i = i + 1
  5. 重复步骤 3 和 4,直到 i >= j,这时候数组被划分成了左右两个部分,左边部分中的所有数都小于等于 pivot,右边部分中的所有数都大于 pivot
  6. 对左右两个部分分别递归进行快速排序,直到每个部分只剩下一个数为止

按照上述步骤进行快速排序后,最终得到的有序数组为 [1, 3, 5, 6, 7, 9]。

2算法实现如下:

int partition(RecType R[],int s,int t)  //一趟划分
{  int i=s,j=t;
   RecType tmp=R[i];	//以R[i]为基准
   while (i<j)  	//从两端交替向中间扫描,直至i=j为止
   {  while (j>i && R[j].key>=tmp.key)
         j--;		//从右向左扫描,找一个小于tmp.key的R[j]
      if(i<j)
      {  R[i]=R[j];	//找到这样的R[j],放入R[i]处
         j--;
      }
      while (i<j && R[i].key<=tmp.key)
         i++;		//从左向右扫描,找一个大于tmp.key的R[i]
      if(i<j)
      {  R[j]=R[i];	//找到这样的R[i],放入R[j]处
         i++;   
      }
   }
   R[i]=tmp;
   return i;
}

void QuickSort(RecType R[],int s,int t)
//对R[s..t]的元素进行快速排序
{  int i;
   if (s<t) 			//区间内至少存在两个元素的情况
   {  i=partition(R,s,t);
      QuickSort(R,s,i-1);	//对左区间递归排序
      QuickSort(R,i+1,t);	//对右区间递归排序
   }
}

3.算法分析

快速排序的时间复杂度为O(nlogn),其中n为待排序元素的数量。空间复杂度则为O(logn)。

4.选择排序

常见的选择排序方法:

(1)简单选择排序(或称直接选择排序)

(2)堆排序

01简单选择排序

1.排序思路:

简单选择排序的排序思路如下:

  1. 从待排序序列中,找到最小元素。

  2. 如果最小元素不是待排序序列的第一个元素,将其和第一个元素互换位置。

  3. 从剩余的待排序元素中继续寻找最小元素,重复步骤2,直到所有元素已经排序完毕。

具体实现时,可以使用两层循环来实现。外层循环依次遍历待排序序列中的每一个元素,内层循环从当前元素的下一个位置开始遍历,找到最小元素后进行交换。

2.算法实现如下:
 

void SelectSort(RecType R[],int n)
{  int i,j,k;
   for (i=0;i<n-1;i++)    	  //做第i趟排序
   {  k=i;
      for (j=i+1;j<n;j++)     //在当前无序区R[i..n-1]中选择key最小的R[k]
	  if (R[j].key<R[k].key)  //k记下目前找到的最小关键字所在的位置
	    k=j; 
      if (k!=i)           	//R[i]和R[k]交换 
         swap(R[i],R[k]);
  }
}

3.算法分析

时间复杂度:O(n^2)

空间复杂度:O(1)

02堆排序

1.算法思路

堆排序是一种基于二叉堆数据结构的排序算法,它将未排序的元素依次插入堆中,然后取出最小(或最大)元素放在已排序的列表末尾,不断重复这个过程直到所有元素被排好序。具体思路如下:

  1. 将待排序数组转换成一个二叉堆,通常使用最小堆进行排序;

  2. 取出堆顶元素(即最小元素),与堆底元素交换位置;

  3. 对剩余的 n-1 个元素再次进行堆化操作,得到新的堆;

  4. 重复步骤 2 和 3 直到排序完成。

堆排序需要维护一个堆数据结构,可以使用数组实现。将数组看成完全二叉树,根节点为第 0 个元素,对于任意一个节点 i,他的左子节点为 2i+1,右子节点为 2i+2。堆排序主要包含两部分:构建堆和排序。

构建堆:

从最后一个非叶子节点开始,向前逐个处理每个节点,保证该节点所在的子树满足堆的性质。具体而言,就是通过向下递归交换节点,使得当前节点比其子节点小(或大,取决于堆的类型)。

排序:

每次取出堆顶元素,放在数组的最后一个位置,然后重新构建堆,再次取出堆顶元素,放在数组的倒数第二个位置,重复这个过程直到排序完成。

2.算法实现如下:

void sift(RecType R[],int low,int high) 	//调整堆的算法
{  int i=low,j=2*i;    	//R[j]是R[i]的左孩子
   RecType tmp=R[i];
   while (j<=high) 
   {
     if (j<high && R[j].key<R[j+1].key) j++;
     if (tmp.key<R[j].key)  	//双亲小
     {  R[i]=R[j];   	     	//将R[j]调整到双亲结点位置
        i=j;         	     	//修改i和j值,以便继续向下筛选
        j=2*i;
     }
     else break;     	      	//双亲大:不再调整
   }
   R[i]=tmp;
}

void HeapSort(RecType R[],int n)
{  int i;
   for (i=n/2;i>=1;i--) 	//循环建立初始堆
       sift(R,i,n); 
   for (i=n; i>=2; i--)	//进行n-1次循环,完成推排序
   {  
       swap(R[1],R[i]);      	//R[1]与R[i]交换
       sift(R,1,i-1);   	//筛选R[1]结点,得到i-1个结点的堆
   }
}

3.算法分析

堆排序的时间复杂度为 O(nlogn),其中 n 表示待排序的元素个数。堆排序中,建堆的时间复杂度为 O(n),每次取出堆顶元素后,需要进行一次堆化操作,时间复杂度为 O(logn),因此,总时间复杂度为 O(nlogn)。

堆排序的空间复杂度为 O(1),即不需要额外的存储空间,只需要在原数组上进行操作。但是,实际上,由于堆排序需要维护一个堆数据结构,这个堆可以使用数组来表示,因此,在实现时会占用一些额外的空间。但是,堆排序的空间复杂度仍然是 O(1),即与输入规模无关。

5.归并排序

归并排序是多次将相邻两个或两个以上的有序表合并成一个新有序表的排序方法。

二路归并排序是多次将相邻两个的有序表合并成一个新有序表的排序方法,是最简单的归并排序。       

1.排序思路

归并排序是一种分治算法,其基本思路是将待排序的序列分成若干个子序列,每个子序列都是有序的,然后再将子序列合并成更大的有序序列,直到最终只剩下一个有序序列。

具体实现步骤如下:

  1. 将待排序的序列不断地二分,直至每个子序列只有一个元素为止;

  2. 将相邻的子序列进行合并,得到新的有序序列;

  3. 不断重复第2步,直至只剩下一个有序序列,这个序列就是排好序的结果。

在合并两个有序序列时,可以使用双指针法。首先定义两个指针分别指向两个有序序列的起始位置,比较两个指针所指元素的大小,将小的放入新的有序序列中,并将该元素所在序列的指针向后移动一位;重复这个操作,直到某一个指针到达了序列的末尾,然后将另一个序列中剩余的元素全部放入新的有序序列中。

2.算法实现如下:

自低向上
void Merge(RecType R[],int low,int mid,int high) 
{  RecType *R1;
   int i=low,j=mid+1,k=0;         //k是R1的下标,i、j分别为第1、2段的下标
   R1=(RecType *)malloc((high-low+1)*sizeof(RecType));
   while (i<=mid && j<=high) 
      if (R[i].key<=R[j].key)  	//将第1段中的元素放入R1中
      {  
         R1[k]=R[i]; 
         i++;k++;
      }
      else            	//将第2段中的元素放入R1中
      {  
         R1[k]=R[j]; 
         j++;k++; 
       }   
  while (i<=mid)         		//将第1段余下部分复制到R1
   {  
      R1[k]=R[i]; 
      i++;
      k++;  
   }
  while (j<=high)        		//将第2段余下部分复制到R1
  {  
      R1[k]=R[j]; 
      j++;k++; 
  }
  for (k=0,i=low;i<=high;k++,i++) 	//将R1复制回R中
    R[i]=R1[k];
  free(R1);
} 


void MergePass(RecType R[],int length,int n)//对整个排序序列进行一趟归并
{  int i;
   for (i=0;i+2*length-1<n;i=i+2*length) //归并length长的两相邻子表 
      Merge(R,i,i+length-1,i+2*length-1);

   if (i+length-1<n-1) 	      //余下两个子表,后者长度小于length
      Merge(R,i,i+length-1,n-1); //归并这两个子表
}

void MergeSort(RecType R[],int n)	//二路归并排序
{  int length;
   for (length=1;length<n;length=2*length)
       MergePass(R,length,n);
}


3.算法分析

归并排序的时间复杂度为 O(nlogn),其中 n 表示待排序序列的长度。具体分析如下:

  1. 分解阶段:每次将待排序序列二分,直到每个子序列只剩一个元素,需要二分 logn 次。

  2. 合并阶段:每次合并两个有序子序列时需要比较大小,将元素放入新序列中,最坏情况需要遍历整个序列,因此时间复杂度为 O(n)。

由于每次合并时需要申请额外的空间存储结果序列,因此归并排序的空间复杂度为 O(n)。

6.基数排序

1.排序思路:

基数排序是一种非比较排序算法,它将待排序的元素拆分成若干位,每一位可以看作是一次排序。基数排序按照低位先排序,然后收集;再按照高位排序,然后再收集;依此类推,直到最高位排序完成。

具体实现步骤如下:

  1. 找到待排序数组中最大的数,确定最大数的位数。

  2. 对每一位进行桶排序,以保证相同位数的元素在同一个桶里。

  3. 对每一位排序完后,按照顺序把所有桶中的元素依次取出来,组成新的序列。

  4. 重复第2和第3步,直到所有位都被排序完毕。

#define MAXE 20		//线性表中最多元素个数
#define MAXR 10		//基数的最大取值
#define MAXD 8			//关键字位数的最大取值
typedef struct node
{    char data[MAXD];		//元素的关键字定义的字符串
     struct node *next;
}  RecType1;        	//单链表中每个结点

 2.算法实现如下:

void RadixSort(RecType1 *&p,int r,int d) 
//p为待排序序列链表指针,r为基数,d为关键字位数
{  RecType1 *head[MAXR], *tail[MAXR], *t;  //定义各链队的首尾指针
   int i, j, k;
   for (i=0;i<d;i--)       		//从低位到高位做d趟排序
   {  for (j=0;j<r;j++)       	//初始化各链队首、尾指针
         head[j]=tail[j]=NULL;
      while (p!=NULL)         	//对于原链表中每个结点循环
      {  
          k=p->data[i]-'0'; 		//找第k个链队
	      if (head[k]==NULL) 		//进行分配,即采用尾插法建立单链表
	       {  
              head[k]=p;
              tail[k]=p;
           }
           else
	       {  
              tail[k]->next=p;
              tail[k]=p;
            }
         p=p->next;       	 	//取下一个待排序的结点
      }
     p=NULL;
     for (j=0;j<r;j++)  //对于每一个链队循环进行收集
   { 
       if (head[j]!=NULL)  
       {  
           if (p==NULL)
	         {  
                p=head[j];
	            t=tail[j];
	         }
	       else
	         { 
               t->next=head[j];
	           t=tail[j];
	         }
	   }
       t->next=NULL; 	//最后一个结点的next域置NULL
 } 

3.算法分析:

基数排序的时间复杂度为O(d*(n+k)),其中d表示最大数的位数,n表示数组大小,k表示每个桶的大小。

基数排序的优点是稳定性好,对于小范围数据排序效率高,适用于数据量不大的情况。缺点是需要大量的存储空间,如果数据范围过大则可能不适合使用。

7.各种排序比较

 

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1、冒泡排序属于稳定排序,是一种借助“交换”进行排序的方法。首先要将第一个记录的关键字和第二个记录的关键字进行比较,若为逆序,则将两个记录交换之,然后比较第二个记录与第三个记录的关键字,以此类推,直至第n-1个记录与第n个记录的关键字进行比较为止,这一过程称为第一趟冒泡排序,其结果使得关键字最大的记录被安置在最后一个记录的位置上;然后进行第二趟冒泡排序,对前N-1个记录进行同样操作;以此类推,直到在一趟排序过程中没有进行过交换记录的操作为止。 2、直接插入排序属于稳定的排序,每次从无序表中取出第一个元素,把它插入到有序表的合适位置,使有序表仍然有序。第一趟将待比较的数值与它的前一个数值进行比较,当前一数值比待比较数值大的情况下继续循环比较,依次进行下去,进行了(n-1)趟扫描以后就完成了整个排序过程,结束该次循环。 3、快速排序属于不稳定排序,是对起泡排序的一种改进。它的基本思想是,通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。假设待排序的序列为{R.[s],R.[s+1],…….,R.[t]},首先任意选取一个记录,然后按下述原则从新排序记录:将关键字较他小的记录都安置在他的位置之前,将所有关键字较他大的记录都安置在他的位置后面。由此可以该“枢轴”记录最后所落的位置i作为分界线,将序列{R[s],R[s+1]…….R[t]}分割成两个子序列{R[s],R[s+1]…..R[i-1]}和{R[i+1]……R[t]},这个过程称作一趟快速排序。一趟快速排序的具体做法是:附设两个指针low和high,它们的初值分别指向数组第一个数据和最后一个数据,将枢轴记录暂存在R[0]的位置上排序过程中只作R[low]或R[high]的单向移动,直至一趟排序结束后再将枢轴记录移至正确位置上。 4、简单选择排序属于不稳定排序,基本思想是,每一趟在n-i+1(i=1,2,…n-1)个记录中选取关键字最小的记录作为有序序列中第i个记录。第i趟简单选择排序是指通过n-i次关键字的比较,从n-i+1个记录中选出关键字最小的记录,并和第i个记录进行交换。共需进行n-1趟比较,直到所有记录排序完成为止。例如:进行第i趟选择时,从当前候选记录中选出关键字最小的k号记录,并和第i个记录进行交换。 5、希尔排序属于不稳定排序,也是一种属插入排序类,它的基本思想是:先将整个待排记录序列分割称为若干个子序列分别进行直接插入排序,待整个序列中记录“基本有序”时,再对全体记录进行一次直接插入排序。希尔排序的一个特点是:子序列的构成不是简单的“逐段分割”,而是将相隔某个“增量”的记录组成一个子序列。 6、堆排序属于不稳定排序,它的基本思想是,先将初始文件R[1..n]建成一个大根堆,此堆为初始的无序区,再将关键字最大的记录R[1](即堆顶)和无序区的最后一个记录R[n]交换,由此得到新的无序区R[1..n-1]和有序区R[n],且满足R[1..n-1].keys≤R[n].key;由于交换后新的根R[1]可能违反堆性质,故应将当前无序区R[1..n-1]调整为堆,然后再次将R[1..n-1]中关键字最大的记录R[1]和该区间的最后一个记录R[n-1]交换,由此得到新的无序区R[1..n-2]和有序区R[n-1..n],且仍满足关系R[1..n- 2].keys≤R[n-1..n].keys,同样要将R[1..n-2]调整为堆。直到无序区只有一个元素为止。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值