7大排序算法-- 直接插入,希尔,冒泡,选择 --精解

目录

直接插入排序:

 直接插入排序的时间复杂度:

冒泡排序:

冒泡排序的时间复杂度: 

希尔排序:

希尔排序--预排序:

希尔排序的时间复杂度:

选择排序:


直接插入排序:

插入排序整体来看还是一个挺简单的排序  可以这么比喻 有一群士兵 每个人都有属于自己的编号 但编号是随机的 现在要求他们迅速地按照编号排成一排 那每个士兵都要根据自己的编号去找属于自己的位置  插入排序的思想 跟这个类似

 如上图 我们一开始的数据是有序的 现在插入个数 我们要保证插入数据后我们的这4个数据也是有序的 我们实现的步骤:

① 把插入的数据有一个变量存储(这里我用tmp)

② 从插入数据的前一个数据依次遍历 与tmp比较 如果比tmp大则该数据往后移一位 如果比tmp小 则tmp填入它的前面

如果说给我们一个数组 我们只需把第一个数据看成有序 第二个数据是插入的数据 把他们排成有序 根据这样的思路以此类推 得出这样的代码:

void InsertSort(int* a, int n)//插入排序
{
	for (int i = 1; i < n; i++)
	{
		int end = i;
		int tmp = a[end];

		while (end > 0)
		{
			if (tmp < a[end - 1])
			{
				a[end] = a[end - 1];
				end--;
			}
			else
			{
                a[end] = tmp;
				break;
			}
		}

	}
}

 我这里是把插入数据的位置作为起始的遍历的位置 把它与前一个数进行比较 当然也可以把插入数据的前一个数据作为比较的起始位置 只不过最后填入数据的时候是 a[end+1]=tmp 具体的边界判断大家下去可以试试

回归我们之前的问题 大家看了上面的代码 觉得这样对嘛? 

其实有一个边界的问题不知道大家是否注意到了没有 

就是假设现在有两个数据 第二个数据为插入的数据 此时我们的end指向第二个数据(end=1) 我们与第一个数据比较 假设比较的结果是 第二个数据 要和 第一个数据 交换 那么交换后end指向第一个数据 的位置(end=0) 此时我们想把tmp放进去 可是 此时还能进入while的循环嘛?  是不是就不能了 可是我们的tmp还没放进去呢 这样不就出问题了吗?

 也许你会想 “那我们把while(end>0)改成while(end>=0)不就行了嘛” 真的这样就可以纠正问题了嘛?

如果改成那样  现在我们继续刚刚的操作 我们此时 end=0 进去了 比较的时候我们与 end-1 位置的数据比较 0-1=-1 这样是不是越界了呢?所以这样是不可行的

咋们正确的做法应该是在while循环的外面进行tmp的填入:

void InsertSort(int* a, int n)//插入排序
{
	for (int i = 1; i < n; i++)
	{
		int end = i;
		int tmp = a[end];

		while (end > 0)
		{
			if (tmp < a[end - 1])
			{
				a[end] = a[end - 1];
				end--;
			}
			else
			{
				break;
			}
		}
		a[end] = tmp;
	}
}

大家这样看 我们此时无论是end=0跳出来 还是比较时跳出来 都会执行tmp的填入 这样不就解决了嘛 大家看是不是这个道理?


 直接插入排序的时间复杂度:

现在大家看完插入排序的代码实现 大家算一算这个插入排序的时间复杂度时多少呢? 

 首先 我们的最坏的时间复杂度时O(n^2) :这种情况就是我们每次进行插入数据的操作 每次插入数据的前面的每个数据都会往后移动 也就是 9 8 7 6 5 4 3 2 1 排成升序

其次 我们最好的时间复杂度是O(n) 也就是 给我们的数据本身就是有序的 也就只会遍历一遍数据就没了

虽然我们最坏的时间复杂度是O(n^2) 但是这并不代表我们的插入排序就不好了 毕竟一个完全没有顺序的数组还是少见 这样看来内 我们的时间复杂也就没那么大了 

我们的直接插入排序整体来讲还是挺好的

稳定性:稳定

冒泡排序:

相信对于这个排序大家应该也不陌生  我们就是 依次遍历 两两比较 每次遍历结束 至少有一个数会被放到它该有的位置  :

void swap(int* p, int* q)
{
	int tmp = *p;
	*p = *q;
	*q = tmp;
}

void BubbleSort(int* a, int n)//我这里的n是总的数组大小
{
	for (int i = 0; i < n - 1; i++)
	{
		for (int j = 0; j < n - 1 - i; j++)
		{
			if (a[j] > a[j + 1])
			{
				swap(a + j, a + j + 1);
			}
		}
	}
}

冒泡排序的时间复杂度: 

这样写出的代码大家来看一下我们的时间复杂度是多少呢?

很显然 我们最坏的时间复杂度:O(n^2)  稳定性:稳定

我们最好的时间复杂度:O(n^2) (因为哪怕我们现在给的数组是有序的 我们还是会全部遍历完才结束)  

那我们是不是可以改进下呢 让它不必每次都要去遍历?

void BubbleSort(int* a, int n)
{
	for (int i = 0; i < n - 1; i++)
	{
        int flag=0;

		for (int j = 0; j < n - 1 - i; j++)
		{
			if (a[j] > a[j + 1])
			{
                flag=1;
				swap(a + j, a + j + 1);
			}
		}

        if(!flag) break;
	}
}

 大家看这样是不是就解决了呢 我们第一遍遍历 如果没有发生交换 那么flag依旧等于0 下一次遍历之前判断 flag是否等于0 如果等于说明 数组已经有序不必再继续遍历了 

那现在我们的插入排序 和冒泡排序(改过后)的时间复杂度是差不多的 那到底哪一个会更好一点内? 

 

大家觉得上面的数组 用插入排序 和 冒泡排序 哪一个会更好呢?

我们比较 他们数之间的比较次数就可以了  大家数一下是多少呢?

插入排序:8次 冒泡排序:7+6次 是不是这样呢 

所以我们这样来看的话 还是我们的直接插入排序会更好一点!   

总的来讲:如果顺序有序的话 那插入和冒泡是一样的  但如果是局部有序的话或者说是接近有序 那么插入排序的适应性就更好 比较次数也更少  

希尔排序:

希尔排序 其实就是一个对我们上面的直接插入排序的一个优化

① 使用预排序

② 在使用直接插入排序 

    首先我们上面不是说了直接插入排序比较适应那些 局部有序 和 接近有序 的数组嘛 所以如果说现在给我们一个数组 它并不满足刚刚的两种情况 那我们是不是可以使用一个预排序 是这个数组里的接近我们的这两种情况  这个时候再使用我们的直接插入排序是不是会更好一点呢? 

希尔排序--预排序:

我们与排序的思想就是 把大的数更快的到后面去  把小的数更快的到前面去 

思想知道了 那我们该怎么去实现它呢?

首先实现预排序 我们要把我们的数据分成一个一个的组(gap) 假设gap=3  (也就是三个间隙为一组)

 

 大家看到我们此时的分组了 其实我们分组的目的就是为了把数据分成几个组 就好像这里我们是 9 5 6 8 一组 1 8 7一组 3 2 4一组 我们对着三组数据分别进行插入排序的操作

如图所示 就是每一组进行一个插入排序的操作 让小往前移动 让大的往后移动 其他组也是一样的道理: 

 还有一组我就不演示了 相信大家应该明白其中的道理了吧

那现在大家想想 我们既然这里gap设置的是3 那如果是其他的值呢 我们的预排序又会有怎样的效果呢?

实际上很显然 当我们的gap越小越接近有序  gap越大 大的数据 可以更快到后面,小的数可以更快到到前面 但他越不接近有序

         int gap=3;
         for(int i=0;i<gap;i++)
         {
           for(int j=i;j<n-gap;j+=gap)
           {
            int end = j;
			int tmp = a[end + gap];
			while (end >= 0)
			{
				if (tmp < a[end])
				{
					a[end + gap] = a[end];
					end -= gap;
				}
				else
				{
					break;
				}
			}
			a[end + gap] = tmp;
           }
         }

这是我们根据思路写出的代码 其实跟直接插入排序的整体结构差不多是不是? 我们外面两层for循环是为了遍历每一组  我们的边界之所以设成 n-gap:

 遍历到 n-gap-1 就可以了 就不用再往后遍历

当然我们还可以这样写 外面的遍历:

        int gap=3;
        for (int i = 0; i < n - gap; i++)
		{
			int end = i;
			int tmp = a[end + gap];
			while (end >= 0)
			{
				if (tmp < a[end])
				{
					a[end + gap] = a[end];
					end -= gap;
				}
				else
				{
					break;
				}
			}
			a[end + gap] = tmp;
		}

 这样写得话 我们就不是一组一组的遍历了 而是遍历完一组 又到下一组 大家看是不是这样?

我们上面不是说了 gap不同排序的效果也不同嘛 那现在假设我们的 gap=1 现在又是什么情况呢?

其实很简单 我们gap=1不就是间隙为1是同一的组嘛 那这样是不是每个数都是一个组里面的这个时候在实现for循环里面的操作不就是 一个插入排序了嘛?

        我们 的希尔排序分为预排序和直接插入排序  我们 gap不同  也就对应前面的两种顺序 那我们是不似乎可以把gap改成不是固定的值内?   

void ShellSort(int* a, int n)//希尔排序 gap不为1 预排序 gap为1 插入排序
{
	int gap = n;
	while (gap > 1)
	{
		gap = gap / 3 + 1;
		for (int i = 0; i < n - gap; i++)
		{
			int end = i;
			int tmp = a[end + gap];
			while (end >= 0)
			{
				if (tmp < a[end])
				{
					a[end + gap] = a[end];
					end -= gap;
				}
				else
				{
					break;
				}
			}
			a[end + gap] = tmp;
		}
	}
}

 我们这里gap的变化并没有固定的标准 只是有些书上是这样建议的 写成其他的也行  我们+1是必须的 因为这样才能保证必定gap会有一次等于 1 

希尔排序的时间复杂度:

预排序: 

gap很大的时候 数据会跳的很快 差不多是O(N)

我们其实gap很大的时候 我们数据跳的也就越快 假设 gap=n/3 那是不是我们的数据只要跳三次就可以到应该的位置上了呢 那我们此时for循环里面的操作是不是可以忽略不记了呢 以后的数不也是一样的道理嘛 这就是为什么是O(N)了

gap很小的时候 我们的数据 已经很接近有序了 这个时候排的话 只有很少的数才会去交换 那我们的时间复杂度是不是也可以约估成O(N)了呢?

我们外面的while循环又要进行多少次呢?

咋们每次 ÷3+1 ÷3+1 其实+1就可以忽略掉了 

是不是就是这样内  n/3/3/3/3/.../3/3=1;

假设 ÷ 了x次 那么 x=log3n  

总结:如果按照这种算法 咋们的希尔排序的时间复杂度就是O(N*log3N) (实际上呢 一本严蔚敏老师的书上 提到的是 按照计算 他的出来希尔排序的比较和移动次数约为n^1.3稳定性:不稳定

选择排序:

选择排序其实挺简单的:

 

 我们遍历这个数组选出最小的放到前面 每次遍历的第一个位置都会跟着选出数据的次数更新

我们选出第一个最小的数据了 那么就把它与第一个数据交换 继续遍历数组但不包括第一个位置 此时选出来的数据就要与第二个位置的数据交换了  大家看能明白嘛?

我这里写的呢 跟一般教材上面的不太一样  我这里写得是同时选两个数 也就是一个最大一个最小 但是呢思路都是一样的

void SelectSort(int* a, int n)
{
	int maxi, mini, right, left;
	
	left = 0;
	right = n - 1;
	while (left < right)
	{
		maxi = mini = left;
		for (int i = left; i < right + 1; i++)
		{
			if (a[mini] > a[i]) mini = i;
			if (a[maxi] < a[i]) maxi = i;
		}

		swap(a + mini, a + left);
		
		swap(a + maxi, a + right);

		left++;
		right--;
	}
}

 这样我们的代买就写完了  不知道大家对照着这个数组看有没有发现上面是否存在问题呢?

这个问题挺难发现的 我们第一次选 maxi=0 mini=1 我们首先mini与left 交换 可是我们 此时maxi上的位置还是我们的9嘛 刚刚的交换不是就把 9 换到mini的位置上了嘛 所以应该是这样的:

void SelectSort(int* a, int n)
{
	int maxi, mini, right, left;
	
	left = 0;
	right = n - 1;
	while (left < right)
	{
		maxi = mini = left;
		for (int i = left; i < right + 1; i++)
		{
			if (a[mini] > a[i]) mini = i;
			if (a[maxi] < a[i]) maxi = i;
		}

		swap(a + mini, a + left);
		if(maxi==left) maxi=mini;  
		swap(a + maxi, a + right);

		left++;
		right--;
	}
}

所以正确的代码应该是这样的 因为我们既然我们的maxi变了 就把他修正一下不就行了嘛 left与mini交换了 maxi=left 那么此时正确的max的值不就应该在mini的位置了嘛

传统的选择排序的时间复杂度:O(N^2) 稳定性:不稳定 


这一块的排序算法就说完了 希望会对大家有帮助!!! 预祝大家都能收到心仪大厂的offer!!! 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值