插入排序:表插入

所谓插入排序之表排序,是利用静态链表的形式,分两步完成排序。

一,对一个有序的循环链表,插入一新的元素,修改每个节点的后继指针的指向,使顺着这个指针的指向,元素是有序的。在这个过程中,我们不移动或交换元素,只是修改指针的指向。

二,顺着指针的指向调整元素的位置,使其在链表中真正做到物理有序。

思路:

  1. 构建一新的结构体类型,使其封装了值域和指针域。并增加一节点,当做头节点,为循环终止创造条件,头节点值域存贮的值应不小于原序列中的最大值。
  2. 初始化静态链表:使第一个节点和头节点构成循环的链表。由于链表中只有一个元素,那当然是有序的。
  3. 把后续的节点依次插入到该循环链表中,调整各节点的指针指向,使其沿着指针方向是有序的。

关键代码:
const int MAX = 1000;    //这里的最大值,应设置好 
typedef struct rec   //Record 静态链表的节点类型 
{
	int data;
	int next;   //静态链表的链域可以这样写,大家应该见过很多次 
}Rec;
void InsertSort(int a[], int n)   //表插入 
{
	Rec *rec = new Rec[n+1];
	for (int i = 0; i < n; i++)  //把数据赋给链表 
	{
		rec[i + 1].data = a[i];
		rec[i + 1].next = 0;
	}
	rec[0].data = MAX;
	rec[0].next = 1;    //头节点和第一个节点构成了循环链表 
	int p, q;
	for (int i = 2; i < n + 1; i++) //修改next域,使其按指针指向有序
	{
		q = 0;
		p = rec[q].next;    //p指向当前待比较的节点,q指向前一个
		while (rec[i].data >= rec[p].data)     // >= 保证了排序的稳定性 
		{
			q = p;
			p = rec[q].next;
		}
		rec[i].next = p;
		rec[q].next = i;
	}
	p = rec[0].next;
	int i=0;
	while(p!=0)  //顺着静态链表的指针指向,回写数据到原数组 
	{
		a[i++]=rec[p].data;
		p=rec[p].next;
	}
	delete[]rec;   //释放空间 
}

小结:
说它是插入排序,是因为,每次都是把一个新的数据插入到原本有序的序列中,并且是从序列的头部开始比较。
大家测试下,看是否有问题。
p.s 也可以把静态链表也调整成物理有序的,比较简单,大家先想下,后续补上。

update: 2014-6-3 11:50
有时候我们需要把rec数组中的1-n号位置的元素调整成物理有序的。那么可以这样做:
	int q,p = rec[0].next;	
	for (int i = 1; i < n + 1; i++)
	{
		while(p < i)
			p = rec[p].next;
		q = rec[p].next;    // q记录下一次待归位节点的下标 
		if (p != i)   //如果p与i相等,则表明已在正确的位置上,那就不需要调整了 
		{
			Rec temp=rec[i];
			rec[i]=rec[p];
			rec[p]=temp;
			
			rec[i].next = p;   //这一句的含义,得理解 
		}
		p = q;
	}

    关于这段代码的理解,有一个难点,理解了这一点,就彻底明白了。正所谓:代码只是思想的一种表现形式。思想清楚了,代码就一定能写正确。这个难点是, 比如,把最小的元素调整到i=1的位置。这就往往需要交换两个位置上的元素,交换很容易。但这带来了一个问题,那就是处于i=1位置上的元素的前驱就无法找到它了,失联了!这不就乱了吗?解决方法是有的,那就是  rec[i].next = p;(注意,此时rec[i]不是原来的那个了,是归位后的,原来那个已经调整到别的位置了)为了不失联,中间多转一步。
    至于while循环的作用,你一定很明白:i之前的都是有序的,若你的位置是在i之前,则只能说明你一定被调整过!

若是加上上面的调整,则表插入的另一种写法是:
const int MAX = 1000;    //这里的最大值,应设置好 
typedef struct rec   //Record 静态链表的节点类型 
{
	int data;
	int next;   //静态链表的链域可以这样写,大家应该见过很多次 
}Rec;
void InsertSort(int a[], int n)   //表插入 
{
	Rec *rec = new Rec[n+1];
	for (int i = 0; i < n; i++)
	{
		rec[i + 1].data = a[i];
		rec[i + 1].next = 0;
	}
	rec[0].data = MAX;
	rec[0].next = 1;    //这使得头节点和第一个节点构成了循环链表 
	int p, q;
	for (int i = 2; i < n + 1; i++) //修改next域,使其按指针指向有序
	{
		q = 0;
		p = rec[q].next;    //p指向当前待比较的节点,q指向前一个
		while (rec[i].data >= rec[p].data)     // >= 保证了排序的稳定性 
		{
			q = p;
			p = rec[q].next;
		}
		rec[i].next = p;
		rec[q].next = i;
	}
	int p = rec[0].next;	
	for (int i = 1; i < n + 1; i++)   //rec中的元素进行重排 
	{
		while(p < i)
			p = rec[p].next;
		q = rec[p].next;    // q记录下一次待归位节点的下标 
		if (p != i)   //如果p与i相等,则表明已在正确的位置上,那就不需要调整了 
		{
			Rec temp=rec[i];
			rec[i]=rec[p];
			rec[p]=temp;
			
			rec[i].next = p;   //这一句的含义,得理解 
		}
		p = q;
	}
	//此时,rec数组中 1-n 号位置的元素已经物理有序了 
	for (int i = 1; i < n + 1; i++)   //这里就直接依次赋值 
		a[i - 1] = rec[i].data;
	delete[]rec;   //释放空间 
}

表插入的意义:从表插入的过程中可以看出,它是不需要进行元素的移动或交换的,取而代之的是指针的变化。这对于复杂数据类型特别有意义。
至于最后提供的元数重排算法,只是为了从理论上说明,可以通过next域,使各元素归位。建议不这样做,直接使用第一种做法。

至于测试,就留给大家了

专栏目录看这里:
  • 数据结构与算法目录
  • c指针



你可能感兴趣的:(数据结构与算法,数据结构与算法,经典算法揭秘)