数据结构及简单算法的总结----之【排序】

 

为了找工作,把这些玩意好好复习,多多总结下基础知识吧,有些算法是直接从别的网站copy过来,我会附加上地址。

 

稳定排序与非稳定排序:

稳定排序即在排序之后,具有相同关键码的元素位置相对不变。反之为非稳定排序。

 

 

一,排序

1.冒泡排序

基本思想:将被排序的数组垂直排成一列,从上到下扫描数组,根据待比较的数组元素的“轻重”,对相邻的元素,遵循“轻”者上浮,“重”者下沉的原则。一遍扫描过后,最重的元素已在最下面。再对前n-1个元素进行同样的操作,如此反复知道最后任何两个元素都满足轻者在上重者在下。

另外还有冒泡排序的改进,双向冒泡排序,同时进行轻的上浮和重的下沉操作。

// 更简单的方法,两层for循环,主要就是交换相邻元素
void Interview::bubbleSort(int array[], int n)
{
	for (int i = 0; i < n; ++i)
	{
		for (int j = 1; j < n - i; ++j)
		{
			if (array[j - 1] > array[j])
			{
				std::swap(array[j - 1], array[j]);
			}
		}
	}
}
void Bubble(DATA array[], int n)
//bubble sort冒泡排序
{
	int permutation = 1;   //permutation 意为排序
	int k = n - 1, j;
	DATA tmp;
	while ((k >= 1) && (permutation == 1)){
		permutation = 0;
		for (j = 0; j < k; j++)
		{
			if (array[j].ID>array[j + 1].ID)
			{
				tmp = array[j];
				array[j] = array[j + 1];
				array[j + 1] = tmp;
				permutation = 1;
			}
		}
		k = k - 1;
	}
}


2.快速排序(不稳定)

 

基本思想:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

 

#include<iostream>

using namespace std;

void Qsort(int a[], int low, int high)
//递归的qsort
{
	// 递归出口
	if (low >= high)
		return;

	int first = low;
	int last = high;
	int key = a[first]; // 选取数组的第一个作为标志

	// 比key小的放在key的左边,比key大的放在key的右边
	while (first < last)
	{
		// 从数组后面向前找,找到一个比key小的值,和first交换
		while (first < last && a[last] >= key)
			--last;
		a[first] = a[last];

		// 从数组first位置向last位置找,找到一个比key大的值,和last交换
		while (first < last && a[first] <= key)
			++first;
		a[last] = a[first];
	}

	// 此时,first位置为分界线,左边都是比key小的,右边都是比key大的
	a[first] = key;

	//std::cout << first << " " << last << std::endl;
	// 递归处理 first左边,和first右边。此时first == last
	Qsort(a, low, first - 1);
	Qsort(a, last + 1, high);
}

int main()
{
	//主函数调用qsort
	int a[] = { 57, 68, 59, 52, 72, 28, 96, 33, 24 };

	Qsort(a, 0, sizeof(a) / sizeof(a[0]) - 1);/*这里原文第三个参数要减1否则内存泄露*/

	for (int i = 0; i<sizeof(a) / sizeof(a[0]); i++)
	{
		cout << a[i] << "";
	}

	return(0);
}/*参考数据结构p274(清华大学出版社,严蔚敏)*/

 

非递归算法:

 

//非递归算法
//数据规模很大时,递归的算法很容易导致栈溢出,改为非递归,模拟栈操作,最大长度为n,每次压栈时先压长度较大的,此时栈深度为logn。
#include <iostream>
using namespace std;

int partition(int *a, int l, int h)
{
	int x = a[l];
	int i = l;
	int j = h+1;
	int temp;
	
	while (i<j)
	{
		while (a[++i]<x&&i<h);
		while(a[--j]>x);
		
		if (i<j)
		{
			temp = a[i];
			a[i] = a[j];
			a[j] = temp;
		}
	}
	
	a[l] = a[j];
	a[j] = x;

	return j;
}

void qsort(int *a, int l, int h)
{
	if (l>=h)
		return;

	int *s = new int[h-l+1];
	int p = 0;
	s[p++] = l;
	s[p++] = h;

	int low,high,q;

	while (p>0)
	{
		high = s[--p];
		low = s[--p];
		if (low>=high)
			break;
	
		q = partition(a, low, high);
 
		if (q-low > high-q)
		{
			s[p++] = low;
			s[p++] = q-1;

			if (high > q)
			{
				s[p++] = q+1;
				s[p++] = high;
			}

		}
		else
		{
			s[p++] = q+1;
			s[p++] = high;
			if (q > low)
			{
				s[p++] = low;
				s[p++] = q-1;
			}
		}
	}
	delete []s;
}

int main()
{
	int a[9] = {9,8,7,6,5,4,3,2,1};
	//int a[9] = {1,2,3,4,5,6,7,8,9};
	qsort(a,0,8);
	 
	for (int i=0; i<9; i++)
		cout<<a[i]<<endl;

	return 0;
}

 

qsort()函数:

 

编译器函数库自带的快速排序函数,在头文件stdlib.h中,函数原型为  

 

void qsort(void *base,int nelem,int width,int (*fcmp)(const void *,const void *));

 

参数: 1 待排序数组首地址

2 数组中待排序元素数量

3 各元素的占用空间大小

4 指向函数的指针,用于确定排序的顺序

 

 

比较函数的名称由用户自己定义,比如comp,定义函数原型为:int comp(const void * , const void *);      函数返回值必须是int,两个参数的类型必须都是const void *类型。由于通过这两个参数传递金龙的参数是无类型的,必须再函数体内要对着两个参数进行强制类型转换,才能得到正确的返回值。不同的类型有不同的处理方法。  假设对数组进行升序排序,需说明,如果两个参数的key相等,则返回0;如果前者key小于后者key值,那么返回负数,否则返回正数。对降序的处理方法类似。

例:

 

以下的比较函数可直接用于对整型数组的排序

 

int comp(const void *a,const void *b)
{
	return *(int *) a - *(int  *)b ;
}

调用形式:

 

qsort(a,100,sizeof(int),comp);

 

3.选择排序

基本思想:每一趟从待排序的数据元素中选出最小(或最大)的一个元素,顺序放在已排好序的数列的最后,直到全部待排序的数据元素排完。

#include<iostream>
#include<time.h>
#include<iomanip>
using namespace std;
const int N = 10;
int main()
{
	int a[N], i, j, temp, b;
	srand(time(NULL));
	for (i = 0; i<N; i++)
		a[i] = rand() % 100;
	for (i = 0; i<N; i++)
		cout << setw(3) << a[i];
	/*
	setw()设置域宽
	比如:
	cout<<setw(5)<<12345<<endl;
	就输出
	12345
	cout<<setw(6)<<12345<<endl;
	输出
	空格+12345

	===详细见iomanip:http://baike.baidu.com/view/1655215.htm?fr=aladdin
	*/
	cout << endl;
	for (i = 0; i<N - 1; i++)
	{
		temp = i;
		for (j = i + 1; j<N; j++)
		{
			if (a[temp]>a[j])temp = j;
		}
		if (i != temp)
		{
			b = a[temp]; a[temp] = a[i]; a[i] = b;
		}
	}
	for (i = 0; i<N; i++)
		cout << setw(3) << a[i]; cout << endl;
}

 

 

 

4.插入排序

基本思想:插入排序的基本操作就是将一个数据插入到已经排好序的有序数据中,从而得到一个新的、个数加一的有序数据,算法适用于少量数据的排序,时间复杂度为O(n^2)。是稳定的排序方法。

具体细分为直接插入排序,和二分法插入排序。二分法需要在选择插入位置时用二分来查找,时间复杂度为n.*logn

2014.9.12 19:54

 

//insertsort.h  
#ifndef INSERTSORT_H_
#define INSERTSORT_H_

#include <vector>
using namespace std;

class InsertSort{
private:
	int len;
	vector<int> list;
public:
	InsertSort(vector<int> _list,int _len);
	void insert_sort();
	void out();
};
#endif /* INSERTSORT_H_ */

//没主函数

#include <iostream>
#include "insertsort.h"
using namespace std;
InsertSort::InsertSort(vector <int> _list,int _len){
	for(int i=0;i<_len;i++)
		list.push_back(_list[i]);
	this->len = _len;
}

void InsertSort::insert_sort(){
	int insertNum;
	for(int i=1;i<len;i++)
	{
		insertNum = list[i];
		int j=i;
		while(j>0 && insertNum < list[j-1])
		{
			list[j] = list[j-1];
			j--;
		}
		list[j]= insertNum;
	}
}

 

 

 

 

 

5.希尔排序(后面可能有跟上面重复的排序,今天刚参考了另一本书)

不稳定排序,方法:取gap=┌gap/2┐,并以此作为间隔,讲数据分为gap个子序列,所有距离为gap的对象放在同一组,gap与2gap,gap+1与2gap+1,分隔组进行插入排序,gap--;直到gap=1;

 

6.堆排序     ( 时间复杂度O(N*logN)     不稳定排序     以下内容来自百度百科)

堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,可以利用数组的特点快速定位指定索引的元素。堆分为大根堆和小根堆,是完全二叉树。大根堆的要求是每个节点的值都不大于其父节点的值,即A[PARENT[i]] >= A[i]。在数组的非降序排序中,需要使用的就是大根堆,因为根据大根堆的要求可知,最大的值一定在堆顶。

由于建初始堆所需的比较次数较多,所以堆排序不适宜于记录数较少的文件。

定义:
n个关键字序列Kl,K2,…,Kn称为(Heap),当且仅当该序列满足如下性质(简称为堆性质):
(1)ki<=k(2i)且ki<=k(2i+1)(1≤i≤ n/2),当然,这是小根堆,大根堆则换成>=号。//k(i)相当于二叉树的非叶子结点,K(2i)则是左子节点,k(2i+1)是右子节点
若将此序列所存储的向量R[1..n]看做是一棵完全二叉树的存储结构,则堆实质上是满足如下性质的完全二叉树:

树中任一非叶子结点的关键字均不大于(或不小于)其左右孩子(若存在)结点的关键字。
【例】关键字序列(10,15,56,25,30,70)和(70,56,30,25,15,10)分别满足堆性质(1)和(2),故它们均是堆,其对应的完全二叉树分别如小根堆示例和大根堆示例所示。


大根堆和小根堆:根结点(亦称为堆顶)的关键字是堆里所有结点关键字中最小者的堆称为小根堆,又称最小堆。根结点(亦称为堆顶)的关键字是堆里所有结点关键字中最大者,称为大根堆,又称最大堆。注意:①堆中任一子树亦是堆。②以上讨论的堆实际上是二叉堆(Binary Heap),类似地可定义k叉堆。

高度:
堆可以被看成是一棵树,结点在堆中的高度可以被定义为从本结点到叶子结点的最长简单下降路径上边的数目;定义堆的高度为树根的高度。我们将看到,堆结构上的一些基本操作的运行时间至多是与树的高度成正比,为O(lgn)。

 

堆排序利用了大根堆(或小根堆)堆顶记录的关键字最大(或最小)这一特征,使得在当前无序区中选取最大(或最小)关键字的记录变得简单。
(1)用大根堆排序的基本思想
① 先将初始文件R[1..n]建成一个大根堆,此堆为初始的无序区
② 再将关键字最大的记录R[1](即堆顶)和无序区的最后一个记录R[n]交换,由此得到新的无序区R[1..n-1]和有序区R[n],且满足R[1..n-1].keys≤R[n].key
③由于交换后新的根R[1]可能违反堆性质,故应将当前无序区R[1..n-1]调整为堆。然后再次将R[1..n-1]中关键字最大的记录R[1]和该区间的最后一个记录R[n-1]交换,由此得到新的无序区R[1..n-2]和有序区R[n-1..n],且仍满足关系R[1..n-2].keys≤R[n-1..n].keys,同样要将R[1..n-2]调整为堆。
……
直到无序区只有一个元素为止。
(2)大根堆排序算法的基本操作:
① 初始化操作:将R[1..n]构造为初始堆;
② 每一趟排序的基本操作:将当前无序区的堆顶记录R[1]和该区间的最后一个记录交换,然后将新的无序区调整为堆(亦称重建堆)。
注意

①只需做n-1趟排序,选出较大的n-1个关键字即可以使得文件递增有序。
②用小根堆排序与利用大根堆类似,只不过其排序结果是递减有序的。堆排序和直接选择排序相反:在任何时刻堆排序中无序区总是在有序区之前,且有序区是在原向量的尾部由后往前逐步扩大至整个向量为止
特点

堆排序(HeapSort)是一树形选择排序。堆排序的特点是:在排序过程中,将R[l..n]看成是一棵完全二叉树的顺序存储结构,利用完全二叉树中双亲结点和孩子结点之间的内在关系(参见二叉树的顺序存储结构),在当前无序区中选择关键字最大(或最小)的记录
区别

直接选择排序中,为了从R[1..n]中选出关键字最小的记录,必须进行n-1次比较,然后在R[2..n]中选出关键字最小的记录,又需要做n-2次比较。事实上,后面的n-2次比较中,有许多比较可能在前面的n-1次比较中已经做过,但由于前一趟排序时未保留这些比较结果,所以后一趟排序时又重复执行了这些比较操作。
堆排序可通过树形结构保存部分比较结果,可减少比较次数。

 

 

//array是待调整的堆数组,i是待调整的数组元素的位置,nlength是数组的长度
//本函数功能是:根据数组array构建大根堆

void HeapAdjust(int array[],int i,int nLength)
{
	int nChild;
	int nTemp;
	for(;2*i+1<nLength;i=nChild)
	{
		//子结点的位置=2*(父结点位置)+1
		nChild=2*i+1;
		//得到子结点中较大的结点
		if(nChild < nLength-1 && array[nChild+1] > array[nChild])
			++nChild;
		//如果较大的子结点大于父结点那么把较大的子结点往上移动,替换它的父结点
		if(array[i] < array[nChild])
		{
			nTemp = array[i];
			array[i] = array[nChild];
			array[nChild] = nTemp;
		}
		else
			//否则退出循环
			break;
	}
}

//堆排序算法
void HeapSort(int array[],int length)
{
	int tmp;
	//调整序列的前半部分元素,调整完之后第一个元素是序列的最大的元素
	//length/2-1是最后一个非叶节点,此处"/"为整除
	for(int i=length/2-1; i>=0; --i)
		HeapAdjust(array,i,length);
	//从最后一个元素开始对序列进行调整,不断的缩小调整的范围直到第一个元素
	for(int i=length-1;i>0;--i)
	{
		//把第一个元素和当前的最后一个元素交换,
		//保证当前的最后一个位置的元素都是在现在的这个序列之中最大的
		///Swap(&array[0],&array[i]);
		tmp = array[i];
		array[i] = array[0];
		array[0] = tmp;
		//不断缩小调整heap的范围,每一次调整完毕保证第一个元素是当前序列的最大值
		HeapAdjust(array,0,i);
	}
}

 

 

 

 

 

 

7.归并排序

和快排一样,采用分治的策略和递归的方法,所谓的归并,就是将两个或两个以上的有序数据合并成一个新的有序的序列。

(1)两路归并

两个数组已经拍好序,然后合并为一个数组。方法就是每次在两个数组头开始找小的,然后放进一个新的数组中。

(2)归并排序

一个乱序的数组,如何用以上介绍的归并思想进行排序呢?

第一步,将数组两两分组,每对数字可以理解为两个只有一个元素的数组,然后进行一次归并。

第二步,上步是2个元素归并,第二步是2组含有2个元素的数组进行归并。

第三步,2个含有4个元素的数组进行归并。。。

。。。

知道最后两组数组归并,排序完成。(画图比较明显)

 

8.计数排序(时间复杂度为O(n))

这个排序方法相对于以上的方法有不同之处,但也不失为一种好的处理问题的方法。

计数排序有以下一些限制条件:

1.输入的线性表的元素属于有限集S.

2.输入的线性表长度有限

 

比如“有一串字母序列,请按照字典的顺序排列,字母有重复,如bbaadfakengdsj.”

根据这些条件,我们可以建立一个大小为24的数组,从a到z,分别对应数组的每一位。

然后从头到尾扫描一遍,扫描的过程中,如扫描到b,则b对应的array[2]++;

输出,从头到尾扫描刚刚保存的数组即可得到结果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值