十大经典排序总结(C++/C 实现)

前言

排序是计算机科学中的一个基本操作,因此学好排序的重要性不用多说。

本文适用于十大经典排序的复习回顾,或作学习时的目录大纲。

1、选择排序

工作原理 :

首先在未排序序列中找到最小元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕

伪代码:

 SELECTION-SORT(A)                      
   for j = 1 to A.Length                              
       i = j                                                          
       key = A(i)                                               
       //select min from the remaining unsorted elements
       for i to A.Length                                 
           if key>A(i)                                        
                 key = A(i)                                   
                 k = i                                            
        A(k) = A(j)                                           
        A(j)  = key     

C++/C 核心代码:

//template<class T>  int
void selection_sort(int A[], int n)
{
	for (int i = 0; i < n; i++)
	{
		int index = i;
		for (int j = i + 1; j < n; j++)
		{
			if (A[index] > A[j])
				index = j;
		}
		if (index != i)
		{
			//swap
			int temp = A[i]; 
			A[i] = A[index];
			A[index] = temp;
		}
	}
}

时间复杂度 : 平均 O(n2)

2、插入排序

工作原理 :

通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常采用in-place排序(即 原位操作,不需要用到额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。

伪代码 :

INSERTION-SORT(A)
   for j = 2 to A.Length
        key = A[j]
        //Insert A[j] into the sorted sequence A[1..j - 1].
        i = j - 1
        while i > 0 and A[i] > key
              A[i + 1] = A[i]
              i = i - 1
        A[i + 1] = key

C++/C 核心代码:

void insert_sort(int *A,int n)
{
	int i;
	int key = 0;
	for (int j = 2; j <= n; j++)
	{
		key = A[j];
		i = j - 1;
		while (i > 0 && A[i] > key)
		{
			A[i + 1] = A[i];
			i--;
		}
		A[i + 1] = key;
	}
}

时间复杂度 :平均 O(n2)

3、冒泡排序

工作原理 :

重复地走访过要排序的数列,一次比较两个相邻元素,如果他们的顺序(如从大到小)错误就把他们交换过来。
-
第一趟把最大的元素放到最后的位置,第二趟把倒数第二大的元素放到倒数第二个的位置,第三趟把第三大的元素放在倒数第三的位置上…依次类推

伪代码 :

BUBBLE-SORT(A)
	for i = 0 to A.Length - 1
		for j = 0 to A.Length - i - 1
			if A[j] > A[j + 1]
				exchange A[j] with A[j + 1]

C++/C 核心代码:

void bubble_sort(int* A, int n)
{
	for (int i = 0; i < n; i++)
	{
		for (int j = 0; j < n - i - 1; j++)
		{
			//swap
			if (A[j] > A[j + 1])
			{
				int temp = A[j];
				A[j] = A[J + 1];
				A[j + 1] = temp;
			}
		}
	}
}

时间复杂度 :平均 O(n2)

4、希尔排序

希尔排序是插入排序的一种又称“缩小增量排序”(Diminishing Increment Sort),是直接插入排序算法的一种更高效的改进版本。

工作原理 :

希尔排序基于插入排序的2种性质进行改进
1、在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率。
2、但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位

按照不同步长(间隔) 对元素进行 插入排序,当刚开始元素很无序的时候,步长最大,所以插入排序的元素个数很少,速度很快;当元素基本有序了,步长很小,插入排序对于有序的序列效率很高

关于希尔排序的时间复杂度,与步长息息相关,因此步长的选择是希尔排序的重要部分。

伪代码 :

SHELL-SORT(A)
   h = 1;
   //The last step(gap) must be 1
   Select-step(gap) 
   while h >= 1
      for i = h to A.Length - 1
         for j = i downto h step h
            if A[j] < A[j - h]
             //swap
             temp = A[j]
             A[j] = A[j - h]
             A[j - h] = temp
       Change-step(gap)       

C++/C 核心代码 :

void shell_sort(int* A, int n)
{
	int h = 1;
	//Calculate the first step
	while (h < n / 3)
		h = h * 3 + 1;

	while (h >= 1)
	{
		for (int i = h; i < n; i++)
		{
			for (int j = i; j >= h; j -= h)
			{
				if (A[j] < A[j - h])
				{
					int temp = A[j];
					A[j] = A[j - h];
					A[j - h] = temp;
				}
			}
		}
		h = h / 3;
	}
}

时间复杂度 :平均 O(n1.5)

5、归并排序

讲归并排序之前一定要讲什么是分治(Divide and Conquer)

分治 :

将原问题分解为几个规模较小但类似于原问题的子问题递归地求解这些子问题,然后再合并这些子问题地解来建立原问题的解
-
因此分治模式在每层递归时都有三个步骤:分解、解决、合并

工作原理 :

归并排序 算法完全遵循分治模式。直观上其操作如下

分解: 分解待排序的n个元素的序列成各具 n 2 \frac{n}{2} 2n个元素的两个子序列
-
解决: 使用归并排序递归地排序两个子序列
-
合并: 合并两个已排序的子序列以产生已排序的答案

当待排序的序列长度为1时,递归“开始回升”, 在这种情况下不要做任何工作, 因为长度为1的每个序列都已排好序

伪代码:

MERGE-SORT(A, x, y, T)
   if y - x > 1
      m = (x + y)/2
      p = x, q = m, i = x
      //let L[x...m - 1] and R[m... y - 1] be new arrays
      MERGE-SORT(A, x, m, T)
      MERGE-SORT(A, m, y, T)
      while p < m or q < y
         if q >= y or (p < m and A[q] >=A[p])
            T[i++] = A[p++]
         else  T[i++] = A[q++]
      for i = x to y - 1 
         A[i] = T[i]   
         

C++/C 核心代码 :

void merge_sort(int* A, int x, int y, int* T)
{
	if (y - x > 1)
	{
		int m = (x + y) / 2;
		int p = x, q = m, i = x;
		merge_sort(A, x, m, T);
		merge_sort(A, m, y, T);
		while (p < m || q < y)
		{
			if (q >= y || (p < m && A[q] >= A[p])) T[i++] = A[p++];
			else
				T[i++] = A[q++];
		}
		for (int i = x; i < y; i++) A[i] = T[i];
	}
}

时间复杂度 :平均 O(nlogn)

6、快速排序

与归并排序一样,快速排序也使用了分治思想

工作原理 :

分解: 数组A被划分为两个(可能为空)子数组,使得其中一个子数组中每一个元素都小于等于设定值,另一个子数组中每一个元素都大于等于设定值
-
解决: 同归递归调用快速排序,对两个子数组进行快速排序
-
合并: 因为子数组都是原址排序(不需要额外一个数组),所以不需要合并操作,数组A已经有序

因为随着设定值选择的不同(通常为首关键字、中关键字和尾关键字),就有不同版本的快速排序,这里选择了中关键字

伪代码:

QUICK-SORT(A, l, r)
   if l < r
      mid =A[(r + l)/2]
      i = l, j = r
      while i<=j
         while A[i] < mid   i++
         while A[j] > mid   j--
         
         if i <= j 
           swap(A[i++], A[j--])
         
         QUICK-SORT(A, l, j)
         QUICK-SORT(A, i, r)
         

C++/C 核心代码 :

//template<typename T>  int
void quicksort(int* A, int l, int r)
{
	if (l < r)
	{
		int mid = A[(r + l) / 2];
		int i = l, j = r;
		while (i <= j)
		{
			while (A[i] < mid) i++;
			while (A[j] > mid) j--;

			if (i <= j)
				swap(A[i++], A[j--]);
		}

		quicksort(A, l, j);
		quicksort(A, i, r);
	}
}

时间复杂度 :平均 O(nlogn)

7、堆排序

堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法

因此需要先介绍什么是堆:

是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆(在堆排序算法中用于升序排列);或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆(在堆排序算法中用于降序排列)

接下来讲解怎么由堆的性质构造堆(heap) :

(二叉)堆 可以被看成一个**近似的完全二叉树**(见《数据结构》)

则树上的每一个结点对应数组中的一个元素,因此可以用一个数组来表示,同时给定一个结点的下标 i ,很容易计算得到它的父结点、左孩子和右孩子的下标

以(a)二叉树和(b)数组形式展现的一个最大堆

结点 i下标
PARENT(i)i>>1( i 2 \frac{i}{2} 2i)
LEFT(i)i<<1(2i)
RIGHT(i)i<<1|1(2i+1)

最大堆性质: 除根以外的所有结点 i 都要满足 A[PARENT(i)] >= A[i] (最小堆性质 A[PARENT(i)] <= A[i])

则需要在插入和删除元素的时候进行维护,即调整元素位置

这里就直接贴出了最小堆的核心代码:

template<class T,int MAXN>
class Heap {
public:
	int heapsize = 1;
	T* heap = new T[MAXN + 1];

	inline int left(int index) { return index << 1; }
	inline int right(int index) { return index << 1 | 1; }
	inline int father(int index) { return index >> 1; }

	void push(T key)
	{
		heap[heapsize] = key;
		heapsize++;
		int i = heapsize - 1;
		while(i!=1)
		{
			if (heap[i] < heap[father(i)])
			{
				swap(heap[i], heap[father(i)]);
				i = father(i);
			}
			else
				break;    
		}
	}

	void pop()
	{
		heap[1] = heap[heapsize - 1];
		heapsize--;
		heap[heapsize] = 0;
		int i = 1;
		while (left(i) < heapsize)
		{
			int small = left(i);
			if (right(i) < heapsize && heap[right(i)] < heap[small])
				small = right(i);
			if (heap[small] < heap[i])
				swap(heap[small], heap[i]), i = small;
			else
				break;
		}
	}

	T top() { return heap[1]; }
};

若像本文这里直接构造了最小(最大)堆,则直接输入,输出即为已排序好的,其他亦可,思路是一致的

时间复杂度 :平均 O(nlogn)

8、计数排序

计数排序是一种牺牲空间换取时间的排序,适合于最大值和最小值的差值不是很大的排序。

工作原理 :

对每一个输入元素 x ,确定小于 x 的元素个数(即 直接统计每个数出现的次数,然后相加得 小于等于 x 的数出现的次数),利用这一信息,就可以直接把 x 放到它在输出排列中应在的位置

伪代码 :

COUNTING-SORT(A, B, k)    //0-k 区间
   let C[0...k] be a new array
   min = min(A)
   for i = 0 to k
       C[i] = 0   //memset
   for j = 1 to A.Length
       C[A[j] - min] += 1
    //C[i] now contains the number of elements equal to i + min
    for i = 1 to k
       C[i] = C[i] + C[i - 1]
    //C[i] now contains the number of elements less than or equal to i + min
    for j = A.Length downto 1
        B[C[A[j] - min]] = A[j]
        C[A[j] - min] -= 1
        

C++/C 核心代码 :

void counting_sort(int* A, int* B, int k, int n)    
{
	int min = A[0];
	int* C = new int[k + 1];
	
	for (int i = 1; i < n; i++)
		min = A[i] < min ? A[i] : min;
	
	for (int i = 0; i <= k; i++)
		C[i] = 0;
	for (int j = 1; j <= n; j++)
		C[A[j] - min] = C[A[j] - min] + 1;
	for (int i = 1; i <= k; i++)
		C[i] = C[i] + C[i - 1];
	
	for (int j = n ; j >= 1; j--)
	{
		B[C[A[j] - min]] = A[j];
		C[A[j] - min] = C[A[j] - min] - 1; 
	}
	delete[] C;
}

时间复杂度 :平均 O(n+k)

9、基数排序

基数排序(radix sort) 又称“桶子法”(bucket sort),顾名思义,它是透过键值的部份资讯,将要排序的元素分配至某些“桶”中,藉以达到排序的作用。

工作原理 :

将所有待比较数值统一为同样的数位长度,数位较短的数前面补零(一般采用10进制)。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后(期中操作员必须保证卡片从容器中被取出时不改变顺序), 数列就变成一个有序序列。

一个由7个3位数组成的列表的基数排序过程。阴影指出了进行排序的位

特殊地,我们也可以用基数排序来对具有多关键字域的记录进行排序,比如日期(年,月,日)。

伪代码 :

RADIX-SORT(A, d)
    for i = 1 to d
        use a stable sort to sort array A on digit i
        

C++/C 核心代码 :

int MaxBit(int* A,int N)
{
	int cnt = 1, r = 10;
	for (int i = 1; i <= N; i++)
		while (A[i] >= r)
			r *= 10, cnt++;
	return cnt;
}

void radix_sort(int *a,int n)
{
	int bit = MaxBit(a,n);     
	int radix = 1;   
	int count[10] = { 0 };     //count
	int* temp = new int[n + 1];
	for (int i = 1; i <= bit; i++)
	{
		memset(count, 0, sizeof(count));
		for (int i = 1; i <= n; i++)
			count[(a[i] / radix) % 10]++;
		for (int i = 1; i < 10; i++)
			count[i] = count[i - 1] + count[i];
		//Ensure that the order of the cards is not changed when they are taken out of the container
		for (int i = n; i >= 1; i--)
		{
			temp[count[(a[i] / radix) % 10]] = a[i];
			count[(a[i] / radix) % 10]--;
		}
		for (int i = 1; i <= n; i++)
			a[i] = temp[i];
		radix *= 10;
	}
	delete[] temp;
}

时间复杂度 :平均 O(n*k)

10、桶排序

桶排序(bucket sort) 与计数排序类似,对输入数据作了某种假设,具体来说,计数排序假设输入数据都属于一个小区间内的整数。而桶排序则假设输入数据是由一个随机过程产生,该过程将元素均匀、独立地分布在[ 0, 1)区间上(详见概率论中均匀分布)
工作原理 :

1、根据待排序集合中最大元素和最小元素的差值范围和映射规则,确定申请的桶个数(将[ 0, 1) 区间划分为 n 个相同大小的子区间);
-
2、遍历待排序集合,将每一个元素移动到对应的桶中;
-
3、对每一个桶中元素进行排序,然后遍历每个桶,按照次序把各个桶中的元素列出来。

伪代码 :

BUCKET-SORT(A)
    n =A.Length
    Construct linked list B
    for i = 1 to n 
        insert A[i] into list B
    for i = 0 to n - 1
        sort list B[i] with insertion sort
    concatenate the lists B[0],B[1],...,B[n - 1] together in order
    

下面为方便以 映射规则 f(x) = x/10 - c ,其中 c 为 min/10 即以间隔大小10来区分不同值域

C++/C 核心代码 :

struct KeyNode
{
	int key;
	struct KeyNode* next;
};

void bucket_sort(int *A, int n)
{
	int c = A[0];
	int max = A[0];
	for (int i = 1; i < n; i++)
	{
		c = c < A[i] ? c : A[i];
		max = max < A[i] ? A[i] : max;
	}
	c = c / 10;
	int bucket_size = max / 10 - c + 1;
	
	KeyNode** bucket_table = new struct KeyNode*[bucket_size];
    //Initialization
	for (int i = 0; i < bucket_size; i++) 
	{    
		bucket_table[i] = new struct KeyNode;
		bucket_table[i]->key = 0;
		bucket_table[i]->next = NULL;
	}
	//insert and sort
	for (int i = 0; i < n; i++) 
	{
		KeyNode* node = new struct KeyNode;
		node->key = A[i];
		node->next = NULL;
		int index = A[i] / 10 - c;
		KeyNode* p = bucket_table[index];
		if (p->key == 0) 
		{
			p->next = node;
			p->key++;
		}
		else 
		{
			while (p->next != NULL && p->next->key <= node->key) 
				p = p->next;

			node->next = p->next;
			p->next = node;
			(bucket_table[index]->key)++;
		}
	}
	//output
	KeyNode* k = NULL;
	for (int i = 0; i < bucket_size; i++) 
	{
		for (k = bucket_table[i]->next; k != NULL; k = k->next) 
		{
			printf("%d ", k->key);
		}
	}
}

时间复杂度 :平均 O(n+k)

总结

稳定排序

假设在待排序的文件中,存在两个或两个以上的记录具有相同的关键字,在用某种排序法排序后,若这些相同关键字的元素的相对次序仍然不变 ,则这种排序方法是稳定的。

则选择,快速,希尔,归属于不稳定排序,其他皆稳定

就地(原地)排序:

若是基本上不需要额外辅助的空间,允许少量额外的辅助变量进行的排序,则为原地排序

排序方法时间复杂度(平均)时间复杂度(最坏)时间复杂度(最好)空间复杂度排序方式稳定性
选择排序O(n2)O(n2)O(n2)O(1)in-place不稳定
插入排序O(n2)O(n2)O(n)O(1)in-place稳定
冒泡排序O(n2)O(n2)O(n)O(1)in-place稳定
希尔排序O(n1.3)O(n2)O(n)O(1)in-place不稳定
归并排序O(nlogn)O(nlogn)O(nlogn)O(n)out-place稳定
快速排序O(nlogn)O(n2)O(nlogn)O(logn)in-place不稳定
堆排序O(nlogn)O(nlogn)O(nlogn)O(1)in-place稳定
计数排序O(n+k)O(n+k)O(n+k)O(k)out-place稳定
基数排序O(n*k)O(n*k)O(n*k)O(n+k)out-place稳定
桶排序O(n+k)O(n2)O(n)O(n+k)out-place稳定

参考:

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
十大经典排序算法中的三种排序算法分别是冒泡排序、希尔排序和选择排序。 冒泡排序是一种简单直观的入门排序算法,其原理是从第一个元素开始,与后面的元素逐个比较,如果顺序不对就交换,直到没有可比较的元素为止。 希尔排序是插入排序的一种高效改进版本,也称为“缩小增量排序”。该算法将记录按下标的一定增量分组,对每组使用直接插入排序算法排序,随着增量的逐渐减小,每组包含的关键词越来越多,当增量减至1时,整个文件被分成一组,排序完成。 选择排序是基于冒泡排序的优化,减少了交换的次数。每次遍历后找出最小/最大的元素,然后与第一个元素交换,再从剩下的元素中重复这个过程。 以上是关于冒泡排序、希尔排序和选择排序的简要介绍。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [c++十大经典排序算法](https://blog.csdn.net/zhoujiajie0521/article/details/122183332)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* [十大经典排序算法C++)](https://blog.csdn.net/qq_52639492/article/details/122106147)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值