关于问答区里面的一个算法问题
前提
我时常会抽空逛逛技术论坛的一些问题区和博客区,看看大家提的问题以及赘述的知识点,然后看到一些问题后秉着求真的态度钻研到底,以至于我最近时常会感觉自己是不是有点钻牛角尖了?但过不久我又想方设法在内心说服自己说对于技术有时候就该较较真,总之人就是这么矛盾的,也希望楼主我在生活中不会变成这样的人,那样会不会找不到女朋友~~~哈哈哈。
正文
今天上班的时候看到问答中有位同学提了个算法问题,乍一看还是个女同学(希望头像是真的啊,不要让我再感觉到这个世界上深深的套路了)?于是下决心必须帮女同学解惑,于是在死了N多脑细胞后有了这篇文章。
题目
$len = count($arr);
for ($i = 0; $i < $len; $i++) {
for ($j = 0; $j < $i; $j++) {
if ($arr[$i] < $arr[$j]) {
$temp = $arr[$i];
$arr[$i] = $arr[$j];
$arr[$j] = $temp;
}
}
}
分析
乍一看,这货绝逼不是冒泡排序,有点像选择排序?又有点像插入排序?(关于什么是选择、插入排序,这里有片非常好的文章,请拿走!)但是仔细分析后我给大家说说其中门道,主要从算法时间复杂度、比较次数、交换次数来说明,首先我们罗列以上排序算法的复杂度和比较次数、交换次数。
名称 时间复杂度 比较次数 交换次数
选择排序 O(n^2) n^2次 0~n次
插入排序 O(n)~O(n^2) n~n^2次 0~n^2次
冒泡排序 O(n^2) n^2次 0~n^2次
必须解释的是,以上 O(n^2) 其实真正的循环次数应该是 (n-1)+(n-2)+..+1 也就是n(n-1)/2次,而O(N^2)表示的是复杂度的数量级。举个例子来说,如果n = 10000,那么 n(n-1)/2 = (n^2 - n) / 2 = (100000000 - 10000) / 2,相对10^8来说,10000小的可以忽略不计了,所以总计算次数约为0.5 * N^2。用O(N^2)就表示了其数量级(忽略前面系数0.5),而在大O的表示中,基本只有几种数量级效率依次从高到低:O(1)、O(n)、O(nLogn)、O(n^2)、O(n^n次方)
真相永远只有一个
接下来看我们上面的这道题,为什么说它不是选择排序,很简单因为他的交换次数不知n次,那么为什么说它不是插入排序,这个算法其实它非常像插入排序,也有很多朋友误以为就是插入算法,接下来我们分析比较次数,理论上如果是插入排序的话,$i之前都是排好序的,所以应该$j交换后比较与他后面的那个数,如果小于那么之后的数也就不用再比较,而这个算法它的比较次数和冒泡一样是恒定的N^2,我们可以简单的用一个倒序和升序的数组去验证它的次数,如下代码(本人是Javaer,所以...)
public void test() {
AtomicInteger a = new AtomicInteger(0);
int[] arr = new int[]{1, 2, 3, 4, 5}; //-------3行
int[] arr = new int[]{5, 4, 3, 2, 1}; //-------4行
for (int i = 0; i < 5; i++) {
for (int j = 0; j < i; j++) {
a.incrementAndGet();
if (arr[i] < arr[j]) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
}
System.out.println("------------" + a.toString() + "==========" + arr.length * (arr.length - 1) / 2);
}
打开第3行代码:------------10==========10
打开第4行代码:------------10==========10
所以可以清楚的看到它并不是插入排序,它比插入排序的性能低,不过我想叫他反向的冒泡排序,因为如果你了解它的比较过程就会明白我为什么会这样叫了,仅仅是比较的过程有点不同,但还是会每个都比较,哈哈~~
哪些该较真,哪些不该较真
接下来,我们说它到底是什么排序?有的朋友就说啦:
我感觉不能说是冒泡排序,1,冒泡排序是比较相邻两个数,2:每一轮排序可以得到最大或者最小的数字,上面的这个在任何时候都没有确定最大和最小数,只是维持了一个排好序的数列
的确,如果用这种定义来说 不能说成冒泡,但我想说的是 算法优劣来讲的话 是研究他的时间复杂度和空间复杂度,对于它叫啥名字 我觉得无所谓-。-哈哈