【数据结构与算法】——排序算法篇

本文探讨了排序算法的重要性,特别是在数据管理和检索效率中的角色。分析了不同场景下选择不同排序算法的优势,如Hash用于快速查找,电话号码和身份证数据的有序性,以及大规模数据的树形结构管理。此外,介绍了冒泡排序、选择排序、直接插入排序、希尔排序、堆排序和快速排序的原理、特性及适用情况,强调了快速排序在多数情况下的高效性,但也有其局限性,如基本有序或逆序数据可能导致最坏情况。最后提到了混合排序算法如std::sort的内部机制,它是如何结合快速排序、插入排序和堆排序的优点来优化性能的。
摘要由CSDN通过智能技术生成

    由于研究生考试的需要,加上我对算法的情有独钟,这段时间一直在研究算法。跟大家分享一些我的经验和想法:一、欢迎大家批评指正我错误的地方;二、欢迎大家补偿自己的见解进来,我如果发现有独到见解的评论,我会编辑添加到文章中来,并注明。希望给大家带来好的知识分享!

    为什么我们需要排序?存放数据就像我们在日常生活中存放东西一样,时不时需要整理一下,你下次拿东西的时候才方便。如果你的东西是一堆乱麻,你自己找个东西估计是很费时间的。

    我什么情况下需要排序?其实很多的情况下,是否使用排序是一个重要的策略问题。很早以前人们使用排序,多数情况下是希望能够使用二分查找在logn的时间内取得想要的数据。乱序的情况下,只能使用顺序查找,需要n的时间才能够完成,平均情况下也是n/2,与logn差距太大。于是 排序+二分查找 成为了早期程序员的数据管理标准配置。但是随着算法理论的推进。现在的情况发生了相当巨大的变化。正如《孙子兵法》中阐述的那样,战争的最高境界是【不战而屈人之兵】,那么排序的最高境界就是【不排】:

    1.【如果仅仅是为了取得数据方便】,那么Hash才是最佳的选择,因为如果使用好的解决Hash冲突方法,能够做到1+a/2 时间内取得数据,其中a为Hash表的填装因子。这远远好于二分查找带给你的logn时间复杂度。当然,你别想在Hash表找最大值,最小值,或者最大的10个数之类的问题,如果你需要这些操作,Hash不应该是你的选择。但通常情况下,人们存放用户数据的时候,往往关心的是如何取出而已。这种单纯的存取关系下,Hash绝对是最好的选择!

    2.【非重复关键字数据】,其实现实生活中这种情况是非常多见的,例如电话号码、身份证数据,他们往往可以成为关键字,而且排序往往是有意义的。如电话号码中0826可能是某个特定的地区,身份证511可能有特定的含义,对这些数据的如果是有序的,往往可以从中取得有用的信息。但是,这些数据真的需要排序吗?在《编程珠玑》中记载了这样一个案例,Boss需要程序员在一台老机子上对1,000,000条电话号码进行排序,要求不超过0.5秒就要完成,可用的内存为1M。问题来了?一来是,那个年代的老机子,1百万条数据,0.5秒排完,即使是快速排序都不可能。二者是,1M内存,就算电话号码是integer都存不下1百万条。面对这样的一个难题,作者是如何解决的呢?答案是根本不排序!首先,使用bit表示电话号码,比如有个电话是 000 000 03 那么就是第3个bit为1,相应的没有电话号码时为0,后面类推。其次,在读取数据放到内存的过程中,电话号码,就能够做到已经有序了,因为 100 100 10 一定是存放在 100 100 11前面的,这样就是天然有序的,所以完全不需要排序了!而我们的程序中,其时有非常多的情况下是非重复关键字的,这种情况下,是可以有更好的解决方案的,不是吗?

    3.【大规模频繁变更数据排序】,有时候我们存了相当大规模的数据, 而且随时可能有新的数据插入进来,或者旧的数据需要更新值,而我们又需要这个数据集是有序的,于是我们会经常调用排序算法来排序。这个大规模数据的情况下,性能往往是关键而敏感的,于是我们开始疯狂的优化排序算法。我们开始尝试各种混合优化排序方法,以期能够提升整个程序的性能。但是这样的工作真的是可取的吗?这样的大规模数据情况下,使用 树 这样的结构其实往往是更加科学的选择。因为诸如 红黑树 一类的高级树形结构往往能够在插入的过程中保持树本身的性质,而不需要调用排序算法。这种自动排序结构比优化排序算法更能从本质上改成程序的效率。

    说了这么多,但很多时候我们还是需要一个高效的排序算法的,至少在我们不明确需求情况下,我们有可用的解决方案,也许以后可用找到更加特定的方案,但我们需要先将一个程序跑起来不是?那么先来看看我们有些什么排序算法:

    上图传说是来自《大话数据结构》一书,但我没看过,这里借来引用特此说明。总体说来,我们有四大类排序算法:插入、选择、交换、合并。这是根据这些算法的基本操作来分类的。其实这上述的算法都是基于“比较” 的算法,还有一类特殊的算法是 基数排序。基于“比较”的算法,算法理论证明至少需要“log(n!) 上取整”次比较,而基数排序可以做到线性时间排序。但是基数排序在实践中并没有很好的表现,而且实现起来比较复杂,所以一直很少应用于实际编程。而我们这里讨论的也主要是“比较”排序。下面先来初看下这些“比较”排序的特征:

    上表同样据说来自《大话数据结构》,特此说明。我们可以看出,最主要的平均时间复杂度上,n²  和 nlogn 是两个重要的分水岭。通常n²  复杂度得排序算法被称为简单排序算法,因为通常能够比较简单地编写出来。而nlogn级的排序算法,被称为高级排序算法,因为通常需要一定算法基础的程序员才能够编写。下面我们来一一分析:

    前奏:引入一个简单的操作函数,交换swap,功能是交换传入的两个值,这个简单的操作可以方便后面的程序编码:

inline void swap(int &a,int &b)
{
	a = a^b;
	b = a^b;
	a = a^b;	
};


    上面的 ^ 是 异或 操作,这个交换实现是一种不使用中间变量进行交换的hack code,娱乐性质和实用性质都有一点儿。

  

1.冒泡排序

   

void bubblesort(int *arr,int n)
{
	for( int i=0; i<n; ++i)
	{
		for(int j=0; j<n-1-i; ++j)
		{
			if( arr[j] > arr[j+1] )
				swap(arr[j],arr[j+1]);
		}	
	}
}


 这个版本的冒泡排序是正统的实现方式,其实可以实现地更加简洁好看一点儿:

void 
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值