表插入排序算法_20230612
- 前言
插入排序过程一般涉及到元素比较和移动两类操作,插入排序的优化目标就是减少比较和移动次数。为了减少比较次数,人们开发出二叉查找插入排序,其比较过程采用折半查找,可大幅减少比较次数;为了减少移动次数,2路插入排序方法进入人们的视野;本文即将介绍的表插入排序,主要借助静态链表形式,借助改变数据域中的next指针(下标)关系最终完成排序任务。
- 表插入排序介绍
表插入排序的记录中需包含next域,它指向本记录的下一个有序记录的位置,整个排序过程仅对next的关系进行梳理,最终形成循环链表。具体看一个例子。
初始状态:
Index域 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|---|
Key域 | INT_MAX | 49 | 38 | 65 | 97 | 76 | 13 | 27 | (49) |
Next 域 | 1 | 0 |
为了插入方便起见,设数组中下标为0的分量为表头结点,并令表头结点记录的关键字取最大的整数INT_MAX,那么表头结点就可以作为整个循环链表的起点和终点,因为它被强制定义为头结点,而且作为最大值位于排序的最后位置,那么就自然而然地形成了环形静态链表。
首先将静态链表中下标为‘1’的分量和表头结点构成一个循环链表,然后将下标‘2’到’n’的分量按记录关键字非递减插入到循环链表中。仍以上面的关键字为例,后续插入循环链表的过程。
i=2, 关键字38插入过程,由于38<49,所以38需要插入到49关键字之前,恰好头结点指向49,所以需要断开链表,插入38值头结点和49之间。
Index域 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|---|
Key域 | INT_MAX | 49 | 38 | 65 | 97 | 76 | 13 | 27 | (49) |
Next 域 | 2 | 0 | 1 |
i=3,关键字65插入过程,由于65>49,所以它位于静态链表最后一个位置,它的next域指向头结点,形成环状静态链表。
Index域 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|---|
Key域 | INT_MAX | 49 | 38 | 65 | 97 | 76 | 13 | 27 | (49) |
Next 域 | 2 | 3 | 1 | 0 |
i=4,关键字97的插入过程,逻辑同上,
Index域 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|---|
Key域 | INT_MAX | 49 | 38 | 65 | 97 | 76 | 13 | 27 | (49) |
Next 域 | 2 | 3 | 1 | 4 | 0 |
i=5,关键字76的插入过程,由于65<76<97,所以76需要插入至65和97两个数字之间,插入完成后的循环链表
Index 域 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|---|
Key域 | INT_MAX | 49 | 38 | 65 | 97 | 76 | 13 | 27 | (49) |
Next 域 | 2 | 3 | 1 | 5 | 0 | 4 |
i=6,关键字13插入过程,由于13<38,它是循环链表中最小的值,所以位于循环链表的第一个位置
Index 域 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|---|
Key域 | INT_MAX | 49 | 38 | 65 | 97 | 76 | 13 | 27 | (49) |
Next 域 | 6 | 3 | 1 | 5 | 0 | 4 | 2 |
i=7 关键字插入过程,13<27<39, 待插入元素位于13和39两个元素之间,
Index 域 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|---|
Key域 | INT_MAX | 49 | 38 | 65 | 97 | 76 | 13 | 27 | (49) |
Next 域 | 6 | 3 | 1 | 5 | 0 | 4 | 7 | 2 |
i=8关键字插入过程,由于49=(49),所以它居于49元素之后,
Index 域 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|---|
Key域 | INT_MAX | 49 | 38 | 65 | 97 | 76 | 13 | 27 | (49) |
Next 域 | 6 | 8 | 1 | 5 | 0 | 4 | 7 | 2 | 3 |
- 表插入数据结构
表插入需要利用静态链表对元素的下标操作,形成一个非递减的循环静态链表。首先需要确定基本的数据结构,数据结构确定以后,才能更有效地利用编程对数据进行操作。定义静态链表每个结点包含两个元素,其一为基本的数据元素RcdType,其二是next指针,它是一个静态指针,储存指向下一个元素的数组下标。
基于上述数据元素定义,然后定义经典链表结构体,它包含一个栈形式的数组,它用于储存基本元素和元素之间的顺序逻辑关系,另外一个元素定义链表中实际所包含的元素数量。
#define MAX_LEN 15
#define MAX_SIZE 20
#define SIZE 100
typedef int KeyType;
typedef char* Record;
typedef struct RcdType
{
KeyType key;
Record value;
} RcdType;
typedef struct SLNode
{
RcdType rc; //record item, rc indicates 'record'
int next; // next indicates the index THIS record points to
} SLNode; //Definition of static link list node
typedef struct SLinkListType
{
SLNode r[SIZE];
int length; //current length of static link list
} SLinkListType;
- 表插入排序算法程序实现
表插入排序的操作对象为表中的元素以及元素之间的下标关系,由于每个元素都需要进行排序,所以外部循环至少需要进行(n-1)次,内部循环是对元素下标之间的关系进行操作,利用循环寻找至当前元素需要插入的合适位置,然后更新下标即可。为了方便操作,定义pre变量记录当前寻找元素的前一位置,定义cur为当前寻找元素所在位置,通过比较操作,最终寻找得到插入的合适位置。
值得一提的是,每次搜索当前元素所在位置,pre的值需要从下标为0的位置开始,cur代表的为循环链表中的实际第一个元素的值。
另外值得关注的点是,程序开始之前,需要先对头结点和第一个元素进行next这两个元素形成一个简单的循环链表,否则后面的比较操作将进入死循环,导致程序栈崩溃。
通过while循环比较,找到cur的前置元素pre,后面通过更新cur和pre所在位置元素的下标,从而实现表插入排序。
void list_insert_sort(SLinkListType *s_list)
{
int i;
int pre;
int cur;
s_list->r[0].next=1;
s_list->r[1].next=0;
for(i=2;i<=s_list->length;i++)
{
pre = 0; //s_list->r[0].next;
cur = s_list->r[pre].next;
while(LQ(s_list->r[cur].rc.key,s_list->r[i].rc.key))//search from the start
{
pre=cur;
cur = s_list->r[pre].next;
}
s_list->r[i].next=s_list->r[pre].next;
s_list->r[pre].next=i;
}
}
我们通过上述程序排序后,可以发现表插入算法排序的结果为有序表,则只能对它进行顺序查找,不能随机查找,为了实现随机查找(折半查找),则需要对记录进行重新排序,记录重新排序的过程理解起来比较复杂,具体的图示实现过程可以参考《数据结构》(严蔚敏)的教材,在此不再赘述。
我们呈现C语言的代码给到读者,请自行理解。
void list_sort_arrange(SLinkListType *s_list)
{
int i;
int p;
int q;
SLNode temp;
p=s_list->r[0].next;
for(i=1;i<s_list->length;i++)
{
while(p<i)
{
p=s_list->r[p].next;
}
q=s_list->r[p].next;
if(p!=i)
{
temp=s_list->r[i];
s_list->r[i]=s_list->r[p];
s_list->r[p]=temp;
s_list->r[i].next=p; //Keet track of next element for next search
}
p=q;
}
return;
}
- 小结
本文回顾了表插入算法的实现方法以及重新排序的算法,加深了对静态链表的理解,同时也对表插入算法的实现机理有了进一步的理解。
参考资料:
- 《数据结构》(严蔚敏)