数据结构代码题之插入排序实现(顺序和链式)
文章主要针对直接插入排序和简单选择排序进行讲解,一般的教材书上都是以顺序表进行两种排序方式的实现,但是对于链式结构的实现介绍内容较少。原因之一便是,对于链表的操作排序会使得操作非常的复杂,同时时间复杂度与顺序表存储的还有不小的差距。
但对于考研数据结构题目来讲,实现链式的排序算法确显得尤为重要,毕竟学习要学明白,才可以在书写算法的时候游刃有余,以防在考试中能够心中有数,较为流畅的书写代码。
下面我们先对直接插入排序进行实现
直接插入排序
算法思路
- 从第一个元素开始,该元素可以认为已经被排序。
这里要非常注意:
有的题目中问到对于当前的关键字序列给出第k趟的插入排序的时候
我们应该明白,插入排序的第一趟是对题目序列原封不动的序列,
正常的插入排序需要在第二趟进行操作。
- 类似于我们日常生活中的打牌操作,在拿到一张牌的时候,我们要首先对手里的牌进行从后向前看,找到合适的位置,将牌插入手中的牌中,插入排序的方式于上述类似;
- 对于待排序元素,将其与前方的元素进行依次比较,在找到一个元素比当前元素小时,其后面的元素的位置便是插入的位置;
- 对于在比较的过程中,比当前元素数值大的元素,需要进行后移,
- 移动完成后,将其合适的位置插入待排序元素;
- 重复上述步骤
- 插入排序结束!
下面是根据上述的步骤进行的动态图演示,便于更好的理解。
下面是相关的柱形图的演示。
算法实现之顺序表存储
针对上述的算法思路,我们对实现方式进行编码设计:
//直接插入排序实现
static void insertSort(int[] arr){
int i,j,temp;//初始化操作
//由于第一趟原封不动,因此从元素位置1开始进行排序
for(i = 1;i<arr.length;i++){//进行趟数循环
if(arr[i] < arr[i-1]){//前面的元素比当前元素大
//存储进行排序的元素
temp = arr[i];
//j从i-1开始,因为i是待排序元素
//若前面的元素比当前元素大,进行后移操作
//j>=0 && arr[j] > temp;代表后退的条件,当前元素比排序元素大
for(j = i-1; j>=0 && arr[j] > temp; j--){
arr[j+1] = arr[j];//进行后移
}
}
//在一趟结束后,对于temp的位置便是在j+1的位置有一个空位置
arr[j+1] = temp;
}
}
直接插入排序链式实现
- 首先是链式序列的结构体代码进行复习:
typedef struct ListNode{
int data;//数据域
struct node *next;//指针域
}ListNode,*LinkList;
-
时间复杂度分析
对于链表的插入排序元素时只要更新相邻节点的指针即可,不需要像数组一样将插入位置后面的元素往后移动,因此插入操作的时间复杂度是 O(1),但是找到插入位置需要遍历链表中的节点,时间复杂度是 O(n),因此链表插入排序的总时间复杂度仍然是 O(n^2)。
实现思路
- 首先是对链表进行判空操作,若为空直接返回即可;
- 其次是我们要对链表进行引入节点,为什么要引入新节点?
答:因为在链表插入排序的过程中,难免要实现在头节点之前要插入元素,但是如何实现在头节点前面进行插入呢,所以要引入一个节点,指向头节点。
- 引入一个哑点dump,使得dump->next指向head(链表的头节点)
- 设置一个lastsorted指针,指向排序序列的最后一个元素,初始时last等于head。
- 设置一个操作指针cur,指向待排序数据,初始时cur指向head->next;
大致初始化操作图片如下:
- 其次是实现逻辑:相比于顺序存储,链式存储不需要进行元素的移动,只需要进行插入便可以,就是操作指针麻烦一些。
- 比较lastsorted和cur指针的数值大小
- 若lastsorted指向的数值 < cur指向的数值,这是正常的操作步骤,不需要进行插入的,因此只需要进行前进移动即可:
lastSorted = lastSorted->next;
cur = lastSorted->next;
- 若lastsorted指向的数值 > cur指向的数值,则为不正常的步骤,需要新建一个插入cur位置的前驱节点pre,需要进行插入,实现代码如下:
//按照从后往前进行添加的方式
lastSorted->next = curr->next;
curr->next = pre->next;
//pre指向cur的前驱
pre->next = curr;
代码或许难以理解如何操作的,下面看图便可:
- 重复上述步骤,直到cur指向null便是结束排序结束
- 返回dump->next为结果链表
结合上述的操作逻辑进行编码如下:
//插入排序(链式实现)
void insertSortLinkList(LinkList * head){
//进行判空操作
if(head == NULL){
return head;
}
//并不为空操作
//设置一个dump指向头节点
ListNode *dump = (ListNode*)malloc(sizeof(ListNode));
dump->next = head;//初始化节点
ListNode *lastSort = head;
ListNode * cur = head->next;//初始化操作指针
//进行操作排序
while(cur != NULL){
//进行lastSort与cur数值比较
if(lastSort->data <= cur->data){
lastSort=lastSort->next;//进行前进
}else{
//若大于
//开辟一个插入位置前序节点指针
ListNode* pre = dump;
//进行pre的位置调节,找到待插入位置的前一个位置
while(pre->next->data <= cur->data){
pre = pre->next;
}
//进行插入
lastSort->next = cur->next;
cur->next = pre->next;
pre->next = cur;
}
//重置cur的位置
cur = lastSort->next;
}
//返回链表
return dump->next;//这里dump->next就代表head
}
代码整合
typedef struct ListNode{
int data;//数据域
struct node *next;//指针域
}ListNode,*LinkList;
//插入排序(链式实现)
void insertSortLinkList(LinkList * head){
//进行判空操作
if(head == NULL){
return head;
}
//并不为空操作
//设置一个dump指向头节点
ListNode *dump = (ListNode*)malloc(sizeof(ListNode));
dump->next = head;//初始化节点
ListNode *lastSort = head;
ListNode * cur = head->next;//初始化操作指针
//进行操作排序
while(cur != NULL){
//进行lastSort与cur数值比较
if(lastSort->data <= cur->data){
lastSort=lastSort->next;//进行前进
}else{
//若大于
//开辟一个插入位置前序节点指针
ListNode* pre = dump;
//进行pre的位置调节,找到待插入位置的前一个位置
while(pre->next->data <= cur->data){
pre = pre->next;
}
//进行插入
lastSort->next = cur->next;
cur->next = pre->next;
pre->next = cur;
}
//重置cur的位置
cur = lastSort->next;
}
//返回链表
return dump->next;//这里dump->next就代表head
}
上述便是对直接插入排序的顺序和链式存储操作讲解。