C语言实现七大排序算法:插入、希尔、选择、堆排序、冒泡、快排、归并(史上最全)

1.插入排序

1.1思路:

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

实际中我们玩扑克牌时,就用了插入排序的思想:

1.2步骤:

1.从第一个元素开始,该元素可以认为已经被排序
2.取下一个元素tem,从已排序的元素序列从后往前扫描
3.如果该元素大于tem,则将该元素移到下一位
4.重复步骤3,直到找到已排序元素中小于等于tem的元素
5.tem插入到该元素的后面,如果已排序所有元素都大于tem,则将tem插入到下标为0的位置
6.重复步骤2~5

1.3代码:

void InsertSort(int* a, int n)//直接插入法,升序算法
{
	for (int i = 1; i < n; i++)
	{
		int tmp = a[i];//tmp表示待插入的数据
		int end = i - 1;//end为tmp前一个数据
		while (end >= 0)//end往前移动,一一判断
		{
			if (tmp >=a[end])//如果tmp更大,直接跳出循环
				break;
			if (tmp < a[end])//如果tmp更小,则end再往前移
			{
				a[end + 1] = a[end];
				end--;
			}
		}
		a[end + 1] = tmp;
		//代码执行到此位置有两种情况:
		//1.待插入元素找到应插入位置(break跳出循环到此)
		//2.待插入元素比当前有序序列中的所有元素都小(while循环结束后到此)
	}
}

插入排序:时间复杂度:o(n^2);空间复杂度:o (1);

2.希尔排序

2.1思路:

希尔排序,先将待排序列进行预排序,使待排序列接近有序,然后再对该序列进行一次插入排序,此时插入排序的时间复杂度为O(N)。(希尔排序本质上是对直接插入排序的升级,即先经过预排序,使数据接近与有序)

2.2步骤:

1.先选定一个小于N的整数gap作为第一增量,然后将所有距离为gap的元素分在同一组,并对每一组的元素进行直接插入排序。然后再取一个比第一增量小的整数作为第二增量,重复上述操作…
2.当增量的大小减到1时,就相当于整个序列被分到一组,进行一次直接插入排序,排序完成。

2.3代码: 

void ShellSort(int* a, int n)//希尔排序
{
	int gap = n;
	while (gap > 1)
	{
		gap = gap / 3 + 1;//当gap等于1的时候实际上就是进行一遍快速排序
        //gap=gap/2;也可以
		for (int i = 0; i < n - gap; i++)
		{
			int tmp = a[i + gap];
			int end = i;
			while (end >= 0)
			{
				if (a[end] <= tmp)
				{
					break;
				}
				else
				{
					a[end + gap] = a[end];
					end -= gap;
				}
			}
			a[end + gap] = tmp;
		}
	}
}

希尔排序:时间复杂度:o(n^1.3);空间复杂度:o(1);

希尔排序的特性总结:

1. 希尔排序是对直接插入排序的优化。
2. gap > 1 时都是预排序,目的是让数组更接近于有序。当 gap == 1 时,数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。
3. 希尔排序的时间复杂度不好计算,因为 gap 的取值方法很多,导致很难去计算,因此在好些树中给出的希尔排序的时间复杂度都不固定:
《数据结构(C语言版)》严蔚敏:

《数据结构-用面相对象方法与C++描述》--- 殷人昆

 3.选择排序

3.1思路:

每次从待排序列中选出一个最小值,然后放在序列的起始位置,直到全部待排数据排完即可。
实际上,我们可以一趟选出两个值,一个最大值一个最小值,然后将其放在序列开头和末尾,这样可以使选择排序的效率快一倍。

3.2步骤:

1.在元素集合 array[i]--array[n-1] 中选择关键码最大 ( ) 的数据元素
2.若它不是这组元素中的最后一个 ( 第一个 ) 元素,则将它与这组元素中的最后一个(第一个)元素交换
3.在剩余的 array[i]--array[n-2] array[i+1]--array[n-1] )集合中,重复上述步骤,直到集合剩余 1 个元素

3.3代码:

void SelectSort(int* a, int left,int right)//选择排序,每次遍历同时选出最大值和最小值
{
	int mini = left;
	int maxi = left;
	while (left < right)
	{
		for (int i = left; i <= right ; i++)
		{
			if (a[i] < a[mini])
				mini = i;
			if (a[i] > a[maxi])
				maxi = i;
		}
		Swap(&a[left], &a[mini]);//交换两个数的值
		if (left == maxi)//注意:但最左边元素是最大的元素时,此时交换right和maxi就会出错
		{
			maxi = mini;//让maxi重新指向最大的元素
		}
		Swap(&a[right], &a[maxi]);
		left++;
		right--;
	} 
}

 选择排序:时间复杂度:o(n^2);空间复杂度:o(1);

4.冒泡排序

4.1思路:

左边大于右边交换一趟排下来最大的在右边

4.2代码:

void BubbleSort(int* a, int n)//冒泡排序
{
	for (int i = 0; i < n; i++)
	{
		for (int j = 1; j < n - i; j++)
		{
			if (a[j] < a[j - 1])
				Swap(&a[j], &a[j - 1]);//交换两个数的值
		}
	}
}

冒泡排序:时间复杂度:o(n^2);空间复杂度:o(1); 

5.堆排序

5.1思路:

堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是通过堆来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆。堆排序需要建立在对堆数据结构十分了解的基础上。

由于篇幅原因这里直接呈现代码,如果读者对堆不太熟悉的,我推荐大家去看这篇博客堆排序

 5.2代码:

void AdjustDown(int* a, int n, int parent)//向下调整算法
{
	int child = parent * 2 + 1;
	while (child < n)
	{
		if (child+1<n&&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 HeapSort(int* a, int n)
{
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)//建堆
		AdjustDown(a, n, i);
	int end = n - 1;
	while (end >= 0)//排序
	{
		Swap(&a[0], &a[end]);
		AdjustDown(a, end, 0);
		end--;
	}
}

堆排序:时间复杂度o(n*logn);空间复杂度o(1); 

6.快速排序 

思路:

快速排序是 Hoare 1962 年提出的一种二叉树结构的交换排序方法,其基本思想为: 任取待排序元素序列中 的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右 子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止 。快速排序大致上可以分为三种思路,也可以分为递归与非递归。

6.1hoare版本(左右指针法)

思路:
1、选出一个key,一般是最左边或是最右边的。
2、定义一个begin和一个end,begin从左向右走,end从右向左走。(需要注意的是:若选择最左边的数据作为key,则需要end先走;若选择最右边的数据作为key,则需要bengin先走)。
3、在走的过程中,若end遇到小于key的数,则停下,begin开始走,直到begin遇到一个大于key的数时,将begin和right的内容交换,end再次开始走,如此进行下去,直到begin和end最终相遇,此时将相遇点的内容与key交换即可。(选取最左边的值作为key)
4.此时key的左边都是小于key的数,key的右边都是大于key的数
5.将key的左序列和右序列再次进行这种单趟排序,如此反复操作下去,直到左右序列只有一个数据,或是左右序列不存在时,便停止操作,此时此部分已有序

代码: 

int PartSort1(int* a, int begin, int end)
{
	int keyi = begin;
	int key = a[begin];
	while (begin < end)
	{
		while (begin < end && a[end] >= key)
			end--;
		while (begin < end && a[begin] <= key)
			begin++;
		Swap(&a[end], &a[begin]);
	}
	Swap(&a[end], &a[keyi]);
	return end;
}
void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
		return;
	int keyi=PartSort1(a, begin, end);
	QuickSort(a, begin, keyi - 1);
	QuickSort(a, keyi + 1, end);
}

6.2打洞法

思路:
挖坑法思路与hoare版本(左右指针法)思路类似
1.选出一个数据(一般是最左边或是最右边的)存放在key变量中,在该数据位置形成一个坑
2、还是定义一个L和一个R,L从左向右走,R从右向左走。(若在最左边挖坑,则需要R先走;若在最右边挖坑,则需要L先走) 

代码: 

int PartSort2(int* a, int begin, int end)
{
	int key= a[begin];
	int hole = begin;
	while (begin < end)
	{
		while (begin < end && a[end] >= key)
			end--;
		a[hole] = a[end];
		hole = end;
		while (begin < end && a[begin] <= key)
			begin++;
		a[hole] = a[begin];
		hole = begin;
	}
	a[hole] = key;
}
void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
		return;
	int keyi=PartSort2(a, begin, end);
	QuickSort(a, begin, keyi - 1);
	QuickSort(a, keyi + 1, end);
}

6.3前后指针法

思路:
1、选出一个key,一般是最左边或是最右边的。
2、起始时,prev指针指向序列开头,cur指针指向prev+1。
3、若cur指向的内容小于key,则prev先向后移动一位,然后交换prev和cur指针指向的内容,然后cur指针++;若cur指向的内容大于key,则cur指针直接++。

如此进行下去,直到cur到达end位置,此时将key和++prev指针指向的内容交换即可。经过一次单趟排序,最终也能使得key左边的数据全部都小于key,key右边的数据全部都大于key。

然后也还是将key的左序列和右序列再次进行这种单趟排序,如此反复操作下去,直到左右序列只有一个数据,或是左右序列不存在时,便停止操作

 

代码: 

int PartSort3(int* a, int begin, int end)
{
	int key = a[begin];
	int prev = begin;
	int cur = begin + 1;
	while (cur <= end)
	{
		if (a[cur] < key)
		{
			prev++;
			Swap(&a[cur], &a[prev]);
		}
		cur++;
	}
	Swap(&a[begin], &a[prev]);
	return prev;
}


void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
		return;
	int keyi=PartSort3(a, begin, end);
	QuickSort(a, begin, keyi - 1);
	QuickSort(a, keyi + 1, end);
}

6.4非递归

思想:

非递归的思想可以借助数据结构栈来实现,我们仍然利用先前所写的partsort函数,三者思路均可。只是在QuickSort函数里面不使用递归。

代码:

int PartSort1(int* a, int begin, int end)//这里我们利用第一种hoare思路
{
	int keyi = begin;
	int key = a[begin];
	while (begin < end)
	{
		while (begin < end && a[end] >= key)
			end--;
		while (begin < end && a[begin] <= key)
			begin++;
		Swap(&a[end], &a[begin]);
	}
	Swap(&a[end], &a[keyi]);
	return end;
}
void QuickSortNonR(int* a, int begin, int end)
{
	ST st;//ST是一个栈,建立一个栈
	InitStack(&st);//初始化栈
	PushStack(&st, end);//将右边界入栈
	PushStack(&st, begin);//将左边界入栈
	while (!StackEmpty(&st))//当栈不为空
	{
		int left = StackTop(&st);//栈顶元素为左边界
		PopStack(&st);//弹出栈顶元素
		int right = StackTop(&st);//栈顶元素为右边界
		PopStack(&st);//弹出栈顶元素
		int keyi = PartSort1(a, left, right);//进行一趟排序
		if (right > keyi + 1)
		{
			PushStack(&st, right);//再将右边区间入栈,类比递归的思想
			PushStack(&st, keyi + 1);
		}
		if (left < keyi - 1)
		{
			PushStack(&st, keyi - 1);//再将左边区间入栈
			PushStack(&st, left);
		}

	}
	DestroyStack(&st);//销毁栈
}

7.归并排序

基本思想:
归并排序( MERGE-SORT )是建立在归并操作上的一种有效的排序算法 , 该算法是采用分治法( Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。 归并排序核心步骤:

 

 

void _MergeSort(int* a, int left, int right, int* tmp)//递归版本
{
	/*if (left == right)
		return;*/
	if (right - left + 1 <= 10)//最小区间优化
	{
		QuickSort(a, left, right);
		return;
	}
	int mid = (left + right) / 2;//将区间一分为二
	_MergeSort(a, left, mid, tmp);
	_MergeSort(a, mid + 1, right, tmp);
	int start1 = left, end1 = mid;
	int start2 = mid + 1, end2 = right;
	int i = left;
	while (start1 <= end1 && start2 <= end2)
	{
		if (a[start1] <=a[start2])
			tmp[i++] = a[start1++];
		else
			tmp[i++] = a[start2++];
	}
	while (start1 <= end1)
	{
		tmp[i++] = a[start1++];
	}
	while (start2 <= end2)
	{
		tmp[i++] = a[start2++];
	}
	memcpy(a + left, tmp + left, sizeof(int) * (right - left + 1));
}

void MergeSort(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	_MergeSort(a, 0, n - 1, tmp);
	free(tmp);
}

排序复杂度汇总

 

 

 

 

  • 18
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C语言中常见的排序算法包括归并排序选择排序、直接插入排序希尔排序冒泡排序快速排序堆排序以及顺序查找和二分查找。这些排序算法各有特点,在不同情况下有着不同的应用场景和性能表现。 归并排序(Merge Sort):采用分治法,将待排序数组递归地分成两部分,分别进行排序,然后合并两个有序数组。 选择排序(Selection Sort):每次从未排序的部分选出最小(或最大)的元素,放到已排序部分的末尾。 直接插入排序(Insertion Sort):将未排序的元素逐个插入到已排序的数组中,直到全部元素有序。 希尔排序(Shell Sort):插入排序的改进版,通过间隔序列对数组进行分组,分组内部进行插入排序,逐步减小间隔直至为1。 冒泡排序(Bubble Sort):依次比较相邻的两个元素,将较大的元素交换到右侧,通过多次遍历,将最大的元素逐步冒泡到数组末尾。 快速排序(Quick Sort):通过选择一个基准元素,将数组分成两部分,左边部分小于基准,右边部分大于基准,然后递归地对左右子数组进行快速排序堆排序(Heap Sort):利用堆的性质进行排序,将数组构建成最大堆或最小堆,然后依次将堆顶元素与最后一个元素交换并重新调整堆,直到整个数组有序。 顺序查找(Sequential Search):逐个遍历数组元素,查找目标元素是否存在。 二分查找(Binary Search):针对有序数组,通过比较目标值与中间值的大小关系,缩小查找范围,直到找到目标值或确定不存在。 这些排序算法和查找方法在实际编程中都有广泛的应用,选择合适的算法可以提高程序的效率和性能。对于排序算法而言,需要考虑数据规模、数据分布、稳定性等因素,而对于查找算法,则需要考虑数据的有序性和查找频率等因素。
好的,以下是这些排序算法C语言实现: 1. 直接插入排序 ```c void insertion_sort(int arr[], int n) { int i, j, key; for (i = 1; i < n; i++) { key = arr[i]; j = i - 1; while (j >= 0 && arr[j] > key) { arr[j + 1] = arr[j]; j = j - 1; } arr[j + 1] = key; } } ``` 2. 希尔排序 ```c void shell_sort(int arr[], int n) { int gap, i, j, temp; for (gap = n / 2; gap > 0; gap /= 2) { for (i = gap; i < n; i++) { temp = arr[i]; for (j = i; j >= gap && arr[j - gap] > temp; j -= gap) arr[j] = arr[j - gap]; arr[j] = temp; } } } ``` 3. 冒泡排序 ```c void bubble_sort(int arr[], int n) { int i, j, temp; for (i = 0; i < n - 1; i++) { for (j = 0; j < n - i - 1; j++) { if (arr[j] > arr[j + 1]) { temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } } ``` 4. 快速排序 ```c void quick_sort(int arr[], int left, int right) { if (left < right) { int pivot = partition(arr, left, right); quick_sort(arr, left, pivot - 1); quick_sort(arr, pivot + 1, right); } } int partition(int arr[], int left, int right) { int pivot = arr[right]; int i = left - 1; for (int j = left; j < right; j++) { if (arr[j] <= pivot) { i++; swap(&arr[i], &arr[j]); } } swap(&arr[i + 1], &arr[right]); return i + 1; } void swap(int *a, int *b) { int temp = *a; *a = *b; *b = temp; } ``` 5. 简单选择排序 ```c void selection_sort(int arr[], int n) { int i, j, min_index; for (i = 0; i < n - 1; i++) { min_index = i; for (j = i + 1; j < n; j++) { if (arr[j] < arr[min_index]) min_index = j; } swap(&arr[min_index], &arr[i]); } } ``` 6. 堆排序 ```c void heap_sort(int arr[], int n) { for (int i = n / 2 - 1; i >= 0; i--) heapify(arr, n, i); for (int i = n - 1; i >= 0; i--) { swap(&arr[0], &arr[i]); heapify(arr, i, 0); } } void heapify(int arr[], int n, int i) { int largest = i; int l = 2 * i + 1; int r = 2 * i + 2; if (l < n && arr[l] > arr[largest]) largest = l; if (r < n && arr[r] > arr[largest]) largest = r; if (largest != i) { swap(&arr[i], &arr[largest]); heapify(arr, n, largest); } } void swap(int *a, int *b) { int temp = *a; *a = *b; *b = temp; } ``` 7. 归并排序 ```c void merge_sort(int arr[], int left, int right) { if (left < right) { int mid = left + (right - left) / 2; merge_sort(arr, left, mid); merge_sort(arr, mid + 1, right); merge(arr, left, mid, right); } } void merge(int arr[], int left, int mid, int right) { int i, j, k; int n1 = mid - left + 1; int n2 = right - mid; int L[n1], R[n2]; for (i = 0; i < n1; i++) L[i] = arr[left + i]; for (j = 0; j < n2; j++) R[j] = arr[mid + 1 + j]; i = 0; j = 0; k = left; while (i < n1 && j < n2) { if (L[i] <= R[j]) { arr[k] = L[i]; i++; } else { arr[k] = R[j]; j++; } k++; } while (i < n1) { arr[k] = L[i]; i++; k++; } while (j < n2) { arr[k] = R[j]; j++; k++; } } ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值