数据结构学习笔记 --- 排序(插入排序、希尔排序)

原创 2012年03月24日 20:37:48

1. 引言 


本文主要讲解一些常见的排序算法。


2. 插入排序

(1 ) 直接插入排序(straight insertion sort)的作法是:每次从无序表中取出第一个元素,把它插入到有序表的合适位置,使有序表仍然有序。

第一趟比较前两个数,然后把第二个数按大小插入到有序表中; 第二趟把第三个数据与前两个数从前向后扫描,把第三个数按大小插入到有序表中;依次进行下去,进行了(n-1)趟扫描以后就完成了整个排序过程。


(2)折半插入排序(binary insertion sort)是对插入排序算法的一种改进,由于排序算法过程中,就是不断的依次将元素插入前面已排好序的序列中。由于前半部分为已排好序的数列,这样我们不用按顺序依次寻找插入点,可以采用折半查找的方法来加快寻找插入点的速度。

    折半插入排序算法的具体操作为:在将一个新元素插入已排好序的数组的过程中,寻找插入点时,将待插入区域的首元素设置为a[low],末元素设置为a[high],则轮比较时将待插入元素与a[m],其中m=(low+high)/2相比较,如果比参考元素小,则选择a[low]到a[m-1]为新的插入区域(即high=m-1),否则选择a[m+1]到a[high]为新的插入区域(即low=m+1),如此直至low<=high不成立,即将此位置之后所有元素后移一位,并将新元素插入a[high+1]。

    折半插入排序算法是一种稳定的排序算法,比直接插入算法明显减少了关键字之间比较的次数,因此速度比直接插入排序算法快,但记录移动的次数没有变,所以折半插入排序算法的时间复杂度仍然为O(n^2),与直接插入排序算法相同。

(3) 2-路插入排序是在折半插入排序的基础上再改进之,其目的是减少排序过程中移动记录的次数,但为此需要n个记录的辅助空间。时间复杂度为O(n^2)理解:所谓的2-路,是指优先插入在序列前面或后面,然后再考虑插入到中间。


#include "ds.h"

#define 	MAX_SIZE 	20			// 一个用作示例的小顺序表的最大长度
typedef		int			KeyType;	// 定义关键字类型为整型
typedef 	int 		InfoType; // 定义其它数据项的类型

#define 	EQ(a,b) 	((a)==(b))
#define 	LT(a,b) 	((a)<(b))
#define 	LQ(a,b) 	((a)<=(b))

struct RedType						// 记录类型
{
	KeyType		key;				// 关键字项
	InfoType	otherinfo;			// 其它数据项,具体类型在主程中定义
};

struct SqList						// 顺序表类型
{
	RedType		r[MAX_SIZE+1];		// r[0]闲置或用作哨兵单元
	int 		length;				// 顺序表长度
};

// 对顺序表L作直接插入排序。算法10.1
void InsertSort(SqList &L)
{
	int 	i, j;
	for (i = 2; i <= L.length; ++i)
	{
		if (LT(L.r[i].key, L.r[i-1].key))	// "<",需将L.r[i]插入有序子表
		{
			L.r[0] = L.r[i];				// 复制为哨兵
			for (j = i - 1; LT(L.r[0].key, L.r[j].key); --j)
				L.r[j+1] = L.r[j];			// 记录后移
			L.r[j+1] = L.r[0];				// 插入到正确位置
		}
	}
}

// 对顺序表L作折半插入排序。算法10.2
void BInsertSort(SqList &L)
{
	int i, j, m, low, high;
	for (i = 2; i <= L.length; ++i)
	{
		L.r[0] = L.r[i];			// 将L.r[i]暂存到L.r[0]
		low  = 1;
		high = i - 1;
		while (low <= high)			// 在r[low..high]中折半查找有序插入的位置
		{
			m = (low + high)/2;		// 折半
			if LT(L.r[0].key, L.r[m].key)
				high = m - 1;		// 插入点在低半区
			else
				low = m + 1;		// 插入点在高半区
		}
		for (j = i - 1; j >= high + 1; --j)
			L.r[j+1] = L.r[j];		// 记录后移
		L.r[high+1] = L.r[0];		// 插入
	}
}

// 2_路插入排序
void P2_InsertSort(SqList &L)
{
	int i, j, first, final;
	RedType *d;
	d = (RedType*)malloc(L.length*sizeof(RedType)); // 生成L.length个记录的临时空间
	d[0] = L.r[1]; 		// 设L的第1个记录为d中排好序的记录(在位置[0])
	first = final = 0;	// first、final分别指示d中排好序的记录的第1个和最后1个记录的位置
	for (i = 2; i <= L.length; ++i)	// 依次将L的第2个~最后1个记录插入d中
	{
		// 待插记录小于d中最小值,插到d[first]之前(不需移动d数组的元素)
		if (L.r[i].key < d[first].key)
		{
			first = (first-1+L.length)%L.length;
			d[first] = L.r[i];
		}
		else if (L.r[i].key > d[final].key)
		{
			final = final + 1;
			d[final] = L.r[i];
		}
		else
		{
			// 待插记录大于d中最小值,小于d中最大值,插到d的中间(需要移动d数组的元素)
			j = final++;	// 移动d的尾部元素以便按序插入记录
			while (L.r[i].key < d[j].key)
			{
				d[(j+1)%L.length] = d[j];
				j = (j-1+L.length)%L.length;
			}
			d[j+1] = L.r[i];
		}
	}
	
	for (i = 1; i <= L.length; i++)			// 把d赋给L.r
		L.r[i] = d[(i+first-1)%L.length];	// 线性关系
		
}	

void print(SqList L)
{
   int i;
   for(i=1;i<=L.length;i++)
     printf("(%d,%d)",L.r[i].key,L.r[i].otherinfo);
   printf("\n");
}

#define N 8
int main()
{
   RedType d[N]={{49,1},{38,2},{65,3},{97,4},{76,5},{13,6},{27,7},{49,8}};
   SqList l1,l2,l3;
   int i;
   for(i=0;i<N;i++) // 给l1.r赋值
     l1.r[i+1]=d[i];
   l1.length=N;
   l2=l3=l1; // 复制顺序表l2、l3与l1相同
   printf("排序前:\n");
   print(l1);
   InsertSort(l1);
   printf("直接插入排序后:\n");
   print(l1);
   BInsertSort(l2);
   printf("折半插入排序后:\n");
   print(l2);
   P2_InsertSort(l3);
   printf("2_路插入排序后:\n");
   print(l3);
}

3. 希尔排序

希尔排序(Shell Sort)是插入排序的一种。是针对直接插入排序算法的改进。该方法又称缩小增量排序,因DL.Shell于1959年提出而得名

排序过程:先取一个正整数d1<n,把所有序号相隔d1的数组元素放一组,组内进行直接插入排序;然后取d2<d1,重复上述分组和排序操作;直至di=1,即所有记录放进一个组中排序为止

  初始:d=5
  49 38 65 97 76 13 27 49* 55 04
  49 13
  |-------------------|
  38 27
  |-------------------|
  65 49*
  |-------------------|
  97 55
  |-------------------|
  76 04
  |-------------------|
  一趟结果
  13 27 49* 55 04 49 38 65 97 76
  d=3
  13 27 49* 55 04 49 38 65 97 76
  13 55 38 76
  |------------|------------|------------|
  27 04 65
  |------------|------------|
  49* 49 97
  |------------|------------|
  二趟结果
  13 04 49* 38 27 49 55 65 97 76
  d=1
  13 04 49* 38 27 49 55 65 97 76
  |----|----|----|----|----|----|----|----|----|
  三趟结果
  04 13 27 38 49* 49 55 65 76 97


#include "ds.h"
/* 希尔排序的基本思想是:先将整个待排序记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录基本有序时,再对全体记录进行一次直接插入排序 */
#define 	MAX_SIZE 	20			// 一个用作示例的小顺序表的最大长度
typedef		int			KeyType;	// 定义关键字类型为整型
typedef 	int 		InfoType; // 定义其它数据项的类型

#define 	EQ(a,b) 	((a)==(b))
#define 	LT(a,b) 	((a)<(b))
#define 	LQ(a,b) 	((a)<=(b))

struct RedType						// 记录类型
{
	KeyType		key;				// 关键字项
	InfoType	otherinfo;			// 其它数据项,具体类型在主程中定义
};

struct SqList						// 顺序表类型
{
	RedType		r[MAX_SIZE+1];		// r[0]闲置或用作哨兵单元
	int 		length;				// 顺序表长度
};

// 对顺序表L作一趟希尔插入排序。本算法是和一趟直接插入排序相比,
// 作了以下修改:
// 1.前后记录位置的增量是dk,而不是1;
// 2.r[0]只是暂存单元,不是哨兵。当j<=0时,插入位置已找到。算法10.4
void ShellInsert(SqList &L, int dk)
{
	int i, j;
	for (i = dk + 1; i <= L.length; i++)
	{
		// 需将L.r[i]插入有序增量子表
		if LT(L.r[i].key, L.r[i-dk].key)
		{
			L.r[0] = L.r[i];		// 暂存在L.r[0]
			for (j = i-dk; j > 0 && LT(L.r[0].key, L.r[j].key); j -= dk)
				L.r[j+dk] = L.r[j];	// 记录后移,查找插入位置
			L.r[j+dk] = L.r[0];		// 插入
		}
	}
}

void print(SqList L)
{
	int i;
	for (i = 1; i <= L.length; i++)
	{
		printf("%d ", L.r[i].key);
	}
	printf("\n");
}

void print1(SqList L)
{
	int i;
	for (i = 1; i <= L.length; i++)
     	printf("(%d,%d)", L.r[i].key, L.r[i].otherinfo);
   	printf("\n");
}

// 按增量序列dlta[0..t-1]对顺序表L作希尔排序。算法10.5
void ShellSort(SqList &L, int dlta[], int t)
{
	int k;
	for (k = 0; k < t; ++k)
	{
		ShellInsert(L, dlta[k]);
		printf("第%d趟排序结果: ", k+1);
     	print(L);
	}
}

#define N 10
#define T 3
int main()
{
   RedType d[N]={{49,1},{38,2},{65,3},{97,4},{76,5},{13,6},{27,7},{49,8},{55,9},{4,10}};
   SqList l;
   int dt[T]={5,3,1}; // 增量序列数组
   for(int i=0;i<N;i++)
     l.r[i+1]=d[i];
   l.length=N;
   printf("排序前: ");
   print(l);
   ShellSort(l,dt,T);
   printf("排序后: ");
   print1(l);
}


【数据结构与算法】内部排序之一:插入排序和希尔排序的N中实现(不断优化,附完整源码)

本来想将所有的内部排序总结为一篇博文,但是随着研究的深入,还是放弃了这个念头,斟前酌后,还是觉得分开来写比较好,具体原因,看完本篇博文也就自然明了了。 本篇文章主要探讨插入排序和希尔排序,之所...
  • mmc_maodun
  • mmc_maodun
  • 2014年02月28日 00:03
  • 5468

数据结构与算法——插入类排序(直接插入排序,希尔排序)

一、直接插入排序 对于一个有序的序列,不断将后面的元素插入前面的有序序列,保持序列继续有序。 对于直接插入排序的思路:将要排序的元素保存,然后逐个和其前面的元素进行比较,如果前面的元素比其大,则将...
  • a879365197
  • a879365197
  • 2015年07月13日 10:34
  • 627

图解"数据结构--内部排序算法"----插入排序:直接插入排序、希尔排序

一、插入排序(Insertion Sort)的基本思想      我的理解:把要排序的记录插入到已排好序的文件中。  标准定义:每次将一个待排序的记录,按其关键字大小插入到前面已经排好...
  • mazhaojuan
  • mazhaojuan
  • 2012年10月29日 09:22
  • 6473

排序算法之直接插入排序和希尔排序

相信许多人和我一样,排序算法看了好几遍,当时看懂了,过几天一些细节又忘记,所以现在讲排序算法做一个总结,从最基本的排序算法展开来,首先分析直接插入排序和希尔排序。 1.直接插入排序 思想:把一个数...
  • lierjin312158950
  • lierjin312158950
  • 2016年04月09日 15:55
  • 716

数据结构实验之排序六:希尔排序 【OJ--3403】

题目描述 我们已经学习了各种排序方法,知道在不同的情况下要选择不同的排序算法,以期达到最好的排序效率;对于待排序数据来说,若数据基本有序且记录较少时,直接插入排序的效率是非常好的,希尔排序就是针...
  • zhang_di233
  • zhang_di233
  • 2015年12月23日 19:05
  • 1088

各种常见的排序,冒泡排序,选择排序,插入排序,希尔排序,堆排序,快速排序,基数排序,桶排序

各种常见的排序 要开始找工作了,把以前学的各种小知识复习一遍,以下是各种常见的排序的简单实现(冒泡排序,选择排序,插入排序,希尔排序,堆排序,快速排序,基数排序,桶排序),至于原理就不写出来了,...
  • xwchao2014
  • xwchao2014
  • 2015年08月04日 16:41
  • 947

【数据结构和算法12】希尔排序

上一章我们学习了冒泡排序、选择排序和插入排序三种基础排序算法,这三种排序算法比较简单,时间复杂度均为O(N2),效率不高。这节我们讨论一个高级排序算法:希尔排序。希尔排序是基于插入排序的,插入排序有个...
  • eson_15
  • eson_15
  • 2016年04月16日 16:31
  • 3066

数据结构 - 希尔排序(Shell's Sort) 详解 及 代码(C++)

数据结构 - 希尔排序(Shell's Sort) 详解 及 代码(C++) 本文地址: http://blog.csdn.net/caroline_wendy/article/details/2...
  • u012515223
  • u012515223
  • 2014年04月23日 14:37
  • 2758

插入排序/希尔排序/快速排序C语言实现

#include #include #include void swap(int *a,int *b) { int tmp=*a; *a=*b; *b=tmp; } void insertS...
  • ZYH920521
  • ZYH920521
  • 2015年06月16日 16:28
  • 483

数据结构学习笔记 --- 排序(插入排序、希尔排序)

1. 引言  本文主要讲解一些常见的排序算法。 2. 插入排序 (1 ) 直接插入排序(straight insertion sort)的作法是:每次从无序表中取出第一个元素...
  • gzw1623231307
  • gzw1623231307
  • 2017年02月22日 07:10
  • 52
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:数据结构学习笔记 --- 排序(插入排序、希尔排序)
举报原因:
原因补充:

(最多只允许输入30个字)