八大排序总结

八大排序

1.直接插入排序

基本思想:

1.就像我们平时玩扑克牌时,摸牌阶段的排序就用到了插入排序的思想

2.用这第n个数与前面的n-1个数进行比较,找到要插入的位置,将其插入(原来位置上的数不会被覆盖,因为提前保存了)

3.原来位置上的数据,依次后移。

代码实现:

void InsertSort(int* a, int n)
{
	assert(a);
 
	for (int i = 0; i < n - 1; ++i)
	{
		int end = i;
		int x=a[end+1];//将end后面的值保存到x里面了
		//将x插入到[0,end]的有序区间
		while (end >= 0)
		{
			if (a[end] > x)
			{
				a[end + 1] = a[end];  //往后挪动一位
				--end;
			}
			else
			{
				break;
			}
		}
		a[end + 1] = x;      //x放的位置都是end的后一个位置
	}
	
}

2.希尔排序

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

逆序有序的数组排序时,时间复杂度为O ( n *n ),此时效率最低

顺序有序的数组排序时,时间复杂度为O ( n ) ,此时效率最高

我们发现,当被排序的对象越接近有序的时候,插入排序的效率最高,那我们是否有办法将数组变成有序后再用插入排序,此时希尔大佬就发现了这个排序算法,并命名为希尔排序。

代码实现:

void ShellSort(int* a, int n)
{
 
	int gap = n;
	while (gap > 1)
	{
		gap /= 2;
 
		for (int i = 0; i < n - gap; i++)
		{
			int end = i;
			int x = a[end + gap];
			while (end >= 0)
			{
				if (a[end] > x)
				{
					a[end + gap] = a[end];
					end -= gap;
				}
				else
				{
					break;
				}
			}
			a[end + gap] = x;
		}
	}
}

3.选择排序

选择排序就是每次从未排好的序列中找到最大或者最小的,将他放在最右边或者最左边,知道全部有序

void SelectSort(int* a, int n)
{
	int begin = 0,end = n - 1;
 
	while (begin<end)
	{
		int mini = begin, maxi = begin;
 
		for (int i = begin; i <= end; i++)
		{
			if (a[i] < a[mini])
			{
				mini = i;
			}
			if (a[i] > a[maxi])
			{
				maxi = i;
			}
		}
		Swap(&a[mini], &a[begin]);
		//当begin==maxi时,最大值会被换走,修正一下
        //这里就是如果我们最大值就在最开始的位置,那么我们在上面已经将他交换位置了,所以要找到现在最大值所在的位置
		if (begin==maxi)
		{
			maxi=mini;
		}
		Swap(&a[maxi], &a[end]);
		begin++;
		end--;
	}
}//这个代码要比之前大一写的那个好的多,那时候只是找到最小值,一直排下去。

4.堆排序(需要再看看)

基本思想:

1、将待排序的序列构造成一个大堆,根据大堆的性质,当前堆的根节点(堆顶)就是序列中最大的元素;

2、将堆顶元素和最后一个元素交换,然后将剩下的节点重新构造成一个大堆;

3、重复步骤2,如此反复,从第一次构建大堆开始,每一次构建,我们都能获得一个序列的最大值,然后把它放到大堆的尾部。最后,就得到一个有序的序列了。

小结论:

排升序,建大堆

排降序,建小堆

#include <iostream>
#include <algorithm>
using namespace std;

int n;
const int N = 100010;
int h[N], size_;

void down(int u) {
    int t = u;  // t记录最小值
    if (2 * u <= size_ && h[2 * u] < h[t]) t = 2 * u; // 左儿子存在,且值比父亲小
    if (2 * u + 1 <= size_ && h[2 * u + 1] < h[t]) t = 2 * u + 1; // // 右儿子存在,且值比父亲小
    if (t != u) {
        swap(h[t], h[u]);
        down(t);
    }
    return;
}

void up(int u) {
    if (u / 2 > 0 && h[u / 2] > h[u]) {
        swap(h[u / 2], h[u]);
        up(u / 2);
    }
    return;
}

int main() {
    cin >> n;
    for (int i = 1; i <= n; i ++) cin >> h[i]; 
    size_ = n;

    // 初始化堆
    for (int i = n / 2; i > 0; i --) down(i); 

    while(n --) {
        cout << h[1] << " ";
        h[1] = h[size_];
        size_ --;
        down(1); 
    }

    return 0;
}

5.冒泡排序

冒泡排序的基本思想:
一趟过程中,前后两个数依次比较,将较大的数字往后推,下一次只需要比较剩下的n-1个数,如此往复 
//优化版本的冒泡排序
void BubbleSort(int* a, int n)
{
	int end = n-1;
	while (end>0)
	{
		int exchange = 0;
		for (int i = 0; i < end; i++)
		{
			if (a[i] > a[i + 1])
			{
				Swap(&a[i], &a[i + 1]);
				exchange = 1;
			}
		}
		if (exchange == 0)//单趟过程中,若没有交换过,证明已经有序,没有必要再排序
		{
			break;
		}
		end--;
	}
}

6.快速排序

void quick_sort(int q[], int l, int r)
{
    if (l >= r) return;

    int i = l - 1, j = r + 1, x = q[l + r >> 1];
    while (i < j)
    {
        do i ++ ; while (q[i] < x);
        do j -- ; while (q[j] > x);
        if (i < j) swap(q[i], q[j]);
    }
    quick_sort(q, l, j), quick_sort(q, j + 1, r);
}

7.归并排序

void merge_sort(int q[], int l, int r)
{
    if (l >= r) return;

    int mid = l + r >> 1;
    merge_sort(q, l, mid);
    merge_sort(q, mid + 1, r);

    int k = 0, i = l, j = mid + 1;
    while (i <= mid && j <= r)
        if (q[i] <= q[j]) tmp[k ++ ] = q[i ++ ];
        else tmp[k ++ ] = q[j ++ ];

    while (i <= mid) tmp[k ++ ] = q[i ++ ];
    while (j <= r) tmp[k ++ ] = q[j ++ ];

    for (i = l, j = 0; i <= r; i ++, j ++ ) q[i] = tmp[j];
}

8.计数排序

又叫非比较排序,又称为鸽巢原理,是对哈希直接定址法的变形应用。

计数排序就是对于一个序列中,找到他的最小值和最大值,在开创一个数组空间,用来统计,其他数字出现的次数,这时我们的关键字不再是i,而是原数组中其他数字,统计数字,最后统一输出,每个数字出现几次就输出几次。

模板一:

void CountSort(int* a, int n)
{
	int min = a[0], max = a[0];//如果不赋值,min和max就是默认随机值,最好给赋值一个a[0]
 
	for (int i=1;i<n;i++)//修正 找出A数组中的最大值和最小值
	{
		if (a[i] < min)
		{
			min=a[i];
		}
		if (a[i]>max)
		{
			 max=a[i];
		}
	}
	int range = max - min + 1;//控制新开数组的大小,以免空间浪费
	int* count = (int*)malloc(sizeof(int) * range);
	memset(count,0, sizeof(int) * range);//初始化为全0
	if (count==NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
 
	//1、统计数据个数
	for (int i=0;i<n;i++)
	{
		count[a[i]-min]++;
	}
	//2、拷贝回A数组
	int j = 0;
	for (int i=0;i<range;i++)
	{
		while (count[i]--)
		{
			a[j++] = i + min;
		}
	}
	free(count);
	count = NULL;
}

模板二:

//朴素版 
void CountSort(int a[], int n){
    int minval = a[0], maxval = a[0];
    for(int i = 0; i < n; i ++){//遍历数组求得最大值和最小值 
        minval = min(minval, a[i]);
        maxval = max(maxval, a[i]);
    }

    int cnt[maxval + 1] = {0};//根据最大值开辟新数组空间 

    for(int i = 0; i < n; i ++) cnt[a[i]] ++;//统计原数组中元素出现的次数 

    for(int i = minval, k = 0; i <= maxval; i ++){
        while(cnt[i] != 0){
            data[k ++] = i;//将排序后的序列赋给原数组 
            cnt[i] --;//i出现的次数减1 
        }
    }
}

堆排序、快速排序、希尔排序、直接选择排序是不稳定的排序算法,而基数排序、冒泡排序、直接插入排序、折半插入排序、归并排序是稳定的排序算法。

posted @ 2023-06-15 11:05  du463  阅读(47)  评论(0)    收藏  举报  来源