数据结构之排序(C++)

1.直接插入排序

基本思想:

  把n个待排序的元素看成一个有序表和一个无序表,开始时有序表中只有一个元素,无序表中有n-1个元素;排序过程即每次从无序表中取出第一个元素,与已经排好序的有序表从右到左依次比较,找到节点应该插入的位置将它插入到有序表中,使之成为新的有序表,重复n-1次完成整个排序过程。

 实例:

  0.初始状态 3,1,5,7,2,4,9,6(共8个数)

     有序表:3;无序表:1,5,7,2,4,9,6

  1.第一次循环,从无序表中取出第一个数 1,把它插入到有序表中,使新的数列依旧有序

     有序表:1,3;无序表:5,7,2,4,9,6

  2.第二次循环,从无序表中取出第一个数 5,把它插入到有序表中,使新的数列依旧有序

     有序表:1,3,5;无序表:7,2,4,9,6

  3.第三次循环,从无序表中取出第一个数 7,把它插入到有序表中,使新的数列依旧有序

     有序表:1,3,5,7;无序表:2,4,9,6

  4.第四次循环,从无序表中取出第一个数 2,把它插入到有序表中,使新的数列依旧有序

     有序表:1,2,3,5,7;无序表:4,9,6

  5.第五次循环,从无序表中取出第一个数 4,把它插入到有序表中,使新的数列依旧有序

     有序表:1,2,3,4,5,7;无序表:9,6

  6.第六次循环,从无序表中取出第一个数 9,把它插入到有序表中,使新的数列依旧有序

     有序表:1,2,3,4,5,7,9;无序表:6

  7.第七次循环,从无序表中取出第一个数 6,把它插入到有序表中,使新的数列依旧有序

     有序表:1,2,3,4,5,6,7,9;无序表:(空)

这里为了好排序,我加了一个哨兵,其作用是当节点找到哨兵,说明未找到

#include<iostream>
using namespace std;


void insertDirect(int a[],int n)
{
	//a[0]是哨兵,默认a[1]已排好序,所以从a[2]开始 
   for(int i = 2;i < n;++i)
   {
   	   a[0] = a[i];
   	   int j = i-1; //j表示的是已排序的序列的起始位置
   	   //从小到大排序 
	   while(j > 0 && a[j] > a[0])
	   {
	   	   a[j+1] = a[j];
	   	   j -= 1;
	   } 
	   a[j+1] = a[0];
   }	
}

void print(int a[],int n)
{
	for(int i = 1;i < n;++i)
	   cout << a[i] << " ";
    cout << endl;
}

int main()
{
	int a[10]={0,4,3,2,1,9,8,5,6,7};
	insertDirect(a,10);
	print(a,10);
	return 0;
}

2.希尔排序

希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。

  简单插入排序很循规蹈矩,不管数组分布是怎么样的,依然一步一步的对元素进行比较,移动,插入,比如[5,4,3,2,1,0]这种倒序序列,数组末端的0要回到首位置很是费劲,比较和移动元素均需n-1次。而希尔排序在数组中采用跳跃式分组的策略,通过某个增量将数组元素划分为若干组,然后分组进行插入排序,随后逐步缩小增量,继续按组进行插入排序操作,直至增量为1。希尔排序通过这种策略使得整个数组在初始阶段达到从宏观上看基本有序,小的基本在前,大的基本在后。然后缩小增量,到增量为1时,其实多数情况下只需微调即可,不会涉及过多的数据移动。

  我们来看下希尔排序的基本步骤,在此我们选择增量gap=length/2,缩小增量继续以gap = gap/2的方式,这种增量选择我们可以用一个序列来表示,{n/2,(n/2)/2...1},称为增量序列。希尔排序的增量序列的选择与证明是个数学难题,我们选择的这个增量序列是比较常用的,也是希尔建议的增量,称为希尔增量,但其实这个增量序列不是最优的。此处我们做示例使用希尔增量。

 

#include<iostream>
using namespace std;


void shellSort(int a[],int n,int k)
{
	int j;
        //默认前k个已排序
	for(int i = k+1;i < n;++i)
	{
	    a[0] = a[i];
	    j = i-k; //同一组中已排序的起始位置
	    while(j > 0 && a[j] > a[0])
	    {
	    	a[j+k] = a[j];
	    	j -= k;
	    }
	    
	    a[j+k] = a[0];
	}
}

void shell(int a[],int n)
{
	int k =  n/2;
	while(k > 0)
	{
		shellSort(a,n,k);
		k = k/2;	
	}
}


void print(int a[],int n)
{
	for(int i = 1;i < n;++i)
	   cout << a[i] << " ";
    cout << endl;
}

int main()
{
	int a[10]={0,4,3,2,1,9,8,5,6,7};
	insertDirect(a,10);
	//shell(a,10);
	print(a,10);
	return 0;
}

 

3.选择排序

选择排序的基本思想:
每趟从待排序的记录中选出关键字最小(或最大,已排序的不需要去动)的记录,顺序放在已排好序的子文件的前面,直到全部记录排序完毕

 

#include<iostream>
using namespace std;

void print(int a[],int n)
{
	for(int i = 0;i < n;++i)
	   cout << a[i] << " ";
    cout << endl;
}

void selectSort(int b[],int n)
{
	int k;
    for(int i = 0;i < n-1;++i)
	{
		k = i;
		//每次循环都找出最大元素 
		for(int j = i+1;j < n;++j)
		{			
			if(b[j] > b[k])
			{
				k = j;
			}
		}
		
		if(k != i)
		{
			int t = b[i];
			b[i] = b[k];
			b[k] = t; 
		}
	}	
}


int main()
{
	int b[10]={0,4,3,2,1,9,8,5,6,7};
	selectSort(b,10);
	print(b,10);
	return 0;
}

4.快速排序

方法其实很简单:分别从初始序列“6  1  2 7  9  3  4  5 10  8”两端开始“探测”。先从右往左找一个小于6的数,再从左往右找一个大于6的数,然后交换他们。这里可以用两个变量i和j,分别指向序列最左边和最右边。我们为这两个变量起个好听的名字“哨兵i”和“哨兵j”。刚开始的时候让哨兵i指向序列的最左边(即i=1),指向数字6。让哨兵j指向序列的最右边(即j=10),指向数字8。

 

       首先哨兵j开始出动。因为此处设置的基准数是最左边的数,所以需要让哨兵j先出动,这一点非常重要(请自己想一想为什么)。哨兵j一步一步地向左挪动(即j--),直到找到一个小于6的数停下来。接下来哨兵i再一步一步向右挪动(即i++),直到找到一个数大于6的数停下来。最后哨兵j停在了数字5面前,哨兵i停在了数字7面前。

 

 

 

       现在交换哨兵i和哨兵j所指向的元素的值。交换之后的序列如下。

        6  1  2  5  9 3  4  7  10  8

 

 

        到此,第一次交换结束。接下来开始哨兵j继续向左挪动(再友情提醒,每次必须是哨兵j先出发)。他发现了4(比基准数6要小,满足要求)之后停了下来。哨兵i也继续向右挪动的,他发现了9(比基准数6要大,满足要求)之后停了下来。此时再次进行交换,交换之后的序列如下。

        6  1  2 5  4  3  9  7 10  8

 

        第二次交换结束,“探测”继续。哨兵j继续向左挪动,他发现了3(比基准数6要小,满足要求)之后又停了下来。哨兵i继续向右移动,糟啦!此时哨兵i和哨兵j相遇了,哨兵i和哨兵j都走到3面前。说明此时“探测”结束。我们将基准数6和3进行交换。交换之后的序列如下。

        3  1 2  5  4  6  9 7  10  8

 

        到此第一轮“探测”真正结束。此时以基准数6为分界点,6左边的数都小于等于6,6右边的数都大于等于6。回顾一下刚才的过程,其实哨兵j的使命就是要找小于基准数的数,而哨兵i的使命就是要找大于基准数的数,直到i和j碰头为止。

 

        OK,解释完毕。现在基准数6已经归位,它正好处在序列的第6位。此时我们已经将原来的序列,以6为分界点拆分成了两个序列,左边的序列是“3  1 2  5  4”,右边的序列是“9  7  10  8”。接下来还需要分别处理这两个序列。因为6左边和右边的序列目前都还是很混乱的。不过不要紧,我们已经掌握了方法,接下来只要模拟刚才的方法分别处理6左边和右边的序列即可。现在先来处理6左边的序列现吧。

 

        左边的序列是“3  1  2 5  4”。请将这个序列以3为基准数进行调整,使得3左边的数都小于等于3,3右边的数都大于等于3。好了开始动笔吧。

 

        如果你模拟的没有错,调整完毕之后的序列的顺序应该是。

        2  1  3  5  4

 

        OK,现在3已经归位。接下来需要处理3左边的序列“2 1”和右边的序列“5 4”。对序列“2 1”以2为基准数进行调整,处理完毕之后的序列为“1 2”,到此2已经归位。序列“1”只有一个数,也不需要进行任何处理。至此我们对序列“2 1”已全部处理完毕,得到序列是“1 2”。序列“5 4”的处理也仿照此方法,最后得到的序列如下。

        1  2  3 4  5  6 9  7  10  8

 

        对于序列“9  7  10  8”也模拟刚才的过程,直到不可拆分出新的子序列为止。最终将会得到这样的序列,如下。

        1  2  3 4  5  6  7  8 9  10

#include<iostream>
using namespace std;


void print(int a[],int n)
{
	for(int i = 0;i < n;++i)
	   cout << a[i] << " ";
    cout << endl;
}


void quickSort(int a[],int left,int right) 
{
   	if(left > right)  return ;
   	int temp = a[left]; //基准数
	int i = left;
	int j = right;
	while(i < j)
	{
		//必须从右开始 
		while(i < j && a[j] >= temp)
		    j--;
	    while(i < j && a[i] <= temp)
	        i++;
	    if(i < j)
	    {
	    	int t = a[i];
	    	a[i] = a[j];
	    	a[j] = t;
	    }
	} 
 	a[left] = a[j];
 	a[j] = temp;
 	quickSort(a,left,i-1);
 	quickSort(a,i+1,right);
}

int main()
{
	int a[10]={0,4,3,2,1,9,8,5,6,7};
	quickSort(a,0,10);
	print(a,10);
	return 0;
}


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值