插入排序可以基于数组、也可以基于链表实现。
基于数组的优化方式是,要插入元素的前面元素都是有序的,基于这个特性,再找插入位置的时候,我们可以使用二分查找法。
代码实现:
#include<stdio.h>
#include<stdlib.h>
#define null NULL
/*
插入排序算法的基本思想:
从数组的第二个元素开始,依次和前面的元素比较,找到要插入的位置,移动元素,插入。
插入排序的时间复杂度 O(n^2).
在插入排序的三种算法中,带哨兵、不带哨兵、折半插入排序 无论做何种改进,该算法的时间复杂度都是O(n^2).
空间复杂度O(1).
插入排序算法是个稳定的排序算法。
*/
//插入排序,不带哨兵的实现方式
void insertSort(int a[],int n){
//从第二元素开始,依次跟前面的元素比较
for(int i = 1;i < n;i++){
//前一个元素大于后一个元素,无序,需要找到元素的插入位置
if(a[i] < a[i-1]){
int temp = a[i];
//移动元素
int j;
for(j = i-1;j >= 0 && a[j] > temp; j--){
a[j+1]=a[j];
}
a[j+1]=temp;
}
}
}
//插入排序,带哨兵的实现方式
//a[] 数组中 a[0]位置为哨兵, 真正数据元素从a[1]开始, n为真实的数组中元素个数
void insertSort2(int a[], int n){
//从第二个位置开始
for(int i = 2; i <= n;i++ ){
if(a[i]<a[i-1]){
a[0]=a[i];
int j;
//移动元素
for(j=i-1;a[j] > a[0];j--){
a[j+1] = a[j];
}
a[j+1]=a[0];
}
}
}
//折半插入排序
void binarySearchInsertSort(int a[],int n){
//插入排序前面的元素均是有序状态, 所以找插入位置我们可以采用二分查找法进行查找
for(int i=1; i<n ; i++){
//无序,需要交换
if(a[i]<a[i-1]){
int temp = a[i];
int low = 0;
int high = i-1;
int mid;
while(low <= high){
mid = (low+high)/2;
//为了保证插入排序的稳定性,当a[mid] == temp 时, 继续往后找即 low = mid+1;
if(a[mid]> temp){
high = mid-1;
}else{
low = mid+1;
}
}
//将 [low,i-1]位置的元素后移
for(int j = i-1 ; j>=low ;j--){
a[j+1]=a[j];
}
//将要插入的元素放到指定位置
a[low]=temp;
}
}
}
//插入排序 (基于链表的实现) 时间复杂度仍为O(n^2) 虽然移动元素的次数少了,但是比较关键字的数量级仍然是O(n^2)
typedef struct LinkNode{
int data;
struct LinkNode *next;
}LinkNode;
//创建一个链表
void createLinkList(LinkNode *head, int a[],int n){
for(int i = 0;i < n; i++){
LinkNode *s = (LinkNode *)malloc(sizeof(LinkNode));
s->data = a[i];
s->next = null;
head->next = s;
head=head->next;
}
}
//针对链表的插入排序 时间复杂度O(n^2)
void insertSortLinkList(LinkNode *head){
LinkNode *comp = head->next;
//后面n-1个元素比较, 找到插入位置, 删除结点,并在指定的插入位置插入。
while(comp->next != null){
LinkNode *start = head;
int flag = 0;
while(start->next != comp->next){
//为了保证插入排序的稳定性, 等号不能要
if(comp->next->data < start->next->data){
flag = 1;
//printf("%d\n",comp->next->data);
//找到了要插入的位置
//1.将原来的结点删除
LinkNode *p = comp->next;
comp->next=p->next;
//加入到start指向结点的后面即可
p->next = start->next;
start->next= p;
break;
}
start = start->next;
}
//flag == 0 代表没有元素被删除插入到某个位置,此时我们需要递进
//flag == 1 代表有元素被删除并插入到前面的某个位置了, 此时comp不需要递进
if(flag == 0){
comp = comp->next;
}
}
}
//打印链表
void printLinkList(LinkNode *head){
while(head->next != null){
printf("%d ",head->next->data);
//别忘了让head指针后移
head = head->next;
}
}
int main(int argc, char *argv[])
{
int array[] = {4,12,1,17,3,24,11};
//insertSort2(array,6);
//for(int i = 1;i <= 6;i++){
// printf("%d\n",array[i]);
//}
//头结点
LinkNode *head = (LinkNode *)malloc(sizeof(LinkNode));
createLinkList(head,array,7);
printLinkList(head);
printf("\n");
insertSortLinkList(head);
printLinkList(head);
return 0;
}
结果测试:
结论:
插入排序无论是基于链表实现,还是基于数组实现,无论如何优化,时间复杂度均为O(n^2).
折半查找的插入排序,虽然找关键字的比较次数少了,但是移动元素的次数并没变,所以时间复杂度仍为O(n^2)级别.
基于链表的插入排序,虽然移动元素的时间复杂度变为了O(1).但是对比关键的次数并没少,所以时间复杂度仍为O(n^2)级别。
空间复杂度都是O(1).