Bootstrap

数据结构——排序(完)

这次,我们来讲解一下关于计数排序,和三路划分及排序的总结。

一:计数排序

原理:

以上面一个数组a为例子:

1.先记录下来每个数字出现的次数------遍历一遍数组 

2.用另一个数组来记录:

3.进行排序:

 

但是呢?这只是0-9的数字,那么数字大的话:eg:100-110的话,难道也是要开0-110的数组吗?这是不是就显得非常地浪费空间啊?所以,我们这里使用一种 相对位置映射计数

比如:

 

代码:

void CountSort(int* a, int n)
{
	//找最大和最小
	int min =a[0], max = a[0];
	for (int i = 0; i < n; i++)
	{
		if (a[i] < min)
		{
			min = a[i];
		}
		if (a[i] >max)
		{
			max = a[i];
		}
	}
    //创建计数数组
	int* CountA = (int*)malloc(sizeof(int) * n);
	if (CountA == NULL)
	{
		perror("malloc fail");
		return;
	}
    //数组初始化为0
	memset(CountA, 0, sizeof(int) * n);

    //算范围+计算次数
	int range =max-min+1;
	for (int i = 0; i <n; i++)
	{
		CountA[a[i] - min]++;
	}

    
	int j = 0;
	for (int i = 0; i < range; i++)
	{
		while (CountA[i]--)
		{
			a[j++] = i + min;
		}
	}
	free(CountA);
}

三路划分: 

之前我们的快速排序是分成两半,但是呢?当我们出现这样的情况时,会出现效率变低。

 当出现大量重复的数字时,那么key是这个重复的数字时,那么就会存在性能下降的情况。

 

所以呢?针对这种情况,我们来学习一下三路划分的思路。

三路划分:顾名思义,就是分成三路。

 思想:

1.当跟key值相等时,往后退

2.比key值小的话,甩到左边。

3.比key值大的话,甩到右边。

4.跟key值相等的话,放中间。

步骤:

 

912. 排序数组 - 力扣(LeetCode)

这道题要求的时间复杂度有一定要求,像插入排序,选择排序,冒泡排序都是O(n^2)的时间复杂度,所以这三种排序都跑不过。

如果使用的是快速排序得到话,用之前我们所说的方法也是跑不过的,这题就可以使用三路划分的方法了。

代码:

//三数取中
 int GetMidNum(int*nums,int left,int right)
 {
    //int mid=(left+right)/2 ;
    int mid=left+rand()%(right-left);
    if(nums[left]<nums[mid])
    {
        if(nums[mid]<nums[right])
        {
            return mid;
        }
        else if(nums[left]<nums[right])
        {
            return right;
        }
        else{
            return left;
        }
    }
    else{
        if(nums[left]<nums[right])
        {
            return left;
        }
        else if(nums[mid]>nums[right])
        {
            return mid;
        }
        else
        {
            return right;
        }
    }
 }
 void Swap(int*p1,int* p2)
 {
    int x=*p1;
    *p1=*p2;
    *p2=x;
 }
void QuickSort(int*nums,int begin,int end)
{
    if(begin>=end)
        return;
     int midi=GetMidNum(nums,begin,end);
    if(midi !=begin)
    {
        Swap(&nums[begin],&nums[midi]);
    }
    int key=nums[begin];
    int left=begin,right=end;
    int cur=begin+1;
    while(cur<=right)
    {
        if(nums[cur]<key)
        {
            Swap(&nums[left],&nums[cur]);
            cur++;
            left++;
        }
        else if(nums[cur]>key)
        {
            Swap(&nums[cur],&nums[right]);
            right--;
        }
        else{
            cur++;
        }
    }
    //[begin,left-1][left,right][right+1,end];
    QuickSort(nums,begin,left-1);
    QuickSort(nums,right+1,end);

}

int* sortArray(int* nums, int numsSize, int* returnSize) {
    srand(time(NULL));
    *returnSize=numsSize;
   
  QuickSort(nums,0,numsSize-1); 

    return nums;

}
//int mid=(left+right)/2 ;
int mid=left+rand()%(right-left);

 

各大排序的总结: 

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

 简单来说,

现在我们来逐一用具体的例子来说明各个排序的稳定性:

冒泡排序:稳定(相等之后不交换位置就做到稳定了)。

简单选择排序:不稳定:

你看当它有两个数字相同时,是不是会将1换到第一个2中,这是不是就2的相对位置就变了。 

 直接插入:稳定

希尔排序:不稳定

情况:相同的数据可能会分到不同的组预排。

堆排序:不稳定

归并排序:稳定 

快速排序:不稳定

 

好了,写了那么久的排序,今天算是完结散花排序部分的啦。(有点多哈哈哈哈哈哈哈幸好坚持写下来了)

到了我们本次的鸡汤部分:

 向阳生长,扎根梦想,相信自己,词典中只有胜利没有失败。向往的地方那是心生的起源,留下的伤痛给了弱小,只有勇气才传给了不怕失败的人。

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;