插入排序—数组、数组存储结构的链表排序算法

插入排序思想

排序时每次将一个待排序元素与前一个已排序的元素比较,若发生逆序,则需要到已好排序的元素序列中寻找一个一个合适的能使其保持有序的位置,找到这个插入位置,需要将其后已排序序列依次后移腾出空位,然后将待排序元素插入;

具体描述:对于一个数组 A[N],当插入第 i 个元素(i>=1) 时,前面的 A[0], A[1], … , A[i-1]是已排好序的元素序列,当比较 A[i]与A[i-1]若发生逆序时,则需要到已排序好续的元素序列中找到插入位置 k(其中0<=k<i-1),找到后 A[k], A[k+1], … , A[i-1]依次向后顺移,A[k]的位置用A[i]取代

算法步骤

以一个普通数组A[N]从小到大插入排序的过程为例:

第1轮:A[0]A[1]比较,如有逆序则将其插入到第1个元素前面,最坏移动2次,形成2个已排序元素的序列

第2轮:A[1]A[2]比较,如有逆序则在2个已排好序元素的序列中找位置插入,最坏移动3次,形成3个已排序元素的序列

第3轮:A[2]A[3]比较,同理如有逆序则在3个已排好序的元素序列中找位置插入,最坏移动4次,形成4个已排序元素的序列

第N-1轮:A[N-2]A[N-1]比较,最坏移动N次,形成N个已经排好序的元素序列, 排序完成

以下图给定的初始序列为例,各轮排序的结果如图:

在这里插入图片描述

时间复杂度

最理想情况:初始的排序元素序列已经有序,每轮只需要与前面已经有序的元素序列的最后一个元素比较,总共比较的次数为n-1,元素的移动的次数为0;

最糟糕情况:待排序元素是逆序的情况下,第i个元素必须与前面的i个元素都做比较,并且每比较1次就要做一次数据移动

总的比较次数为: ∑ i = 1 n − 1 i = 1 + 2 + 3 + . . . + ( n − 1 ) = n ( n − 1 ) 2 ≈ n 2 2 \sum_{i=1}^{n-1} i= 1+2+3+...+(n-1) = \frac{n(n-1)}{2} \approx \frac{n^2}{2} i=1n1i=1+2+3+...+(n1)=2n(n1)2n2

总的移动次数为: ∑ i = 1 n − 1 ( i + 1 ) = 2 + 3 + . . . + n = ( n − 1 ) ( n + 2 ) 2 ≈ n 2 2 \sum_{i=1}^{n-1} (i+1)= 2+3+...+n = \frac{(n-1)(n+2)}{2} \approx \frac{n^2}{2} i=1n1(i+1)=2+3+...+n=2(n1)(n+2)2n2

因此总的时间复杂度为O( n 2 n^2 n2),并且直接插入排序是一种稳定的排序算法,即原本序列中两个相同的元素A和A’,

若A排在A’的前面,则排序后A与A’的前后相对顺序保持不变

插入排序源码

#include <stdio.h>
#include <stdlib.h>

//插入排序:实现从小到大排序
void insertSort(int arr[], int n) {
  int val;
  for (int i = 1; i < n; i++) {
    //若后一个元素比前一个元素小
    if (arr[i] < arr[i - 1]) {
      val = arr[i];
      int j = i - 1;
      do {
        arr[j + 1] = arr[j];
        j--;
      } while (j >= 0 && val < arr[j]);
      arr[j + 1] = val;
    }
  }
}
int output(int arr[], int n) {
  for (int i = 0; i < n; i++) {
    printf("%d, ", arr[i]);
  }
  printf("\n");
}

int main() {
  // int arr[] = {6, 3, 7, 4, 2, 8, 5};
  int arr[] = {2, 5, 6, 7, 1, 4, 8, 9};
  // int arr[] = {1};
  int n = sizeof(arr) / sizeof(int);
  insertSort(arr, n);
  output(arr, n);
  return 0;
}

数组存储结构的链表—插入排序

对于数组存储结构的链表插入排序,元素的插入通过修改元素的指针域实现,不需要移动数据域,避免了普通数组插入排序时批量移动元素的缺陷,相对来说具有较高的效率,当比较次数仍然普通数组的比较次数一样

以下是一个基于数组存储结构的链表,具体排序过程图解如下:

在这里插入图片描述

数组存储结构的链表—插入排序源码

#include <stdio.h>
#include <stdlib.h>

struct Element {
  //数据域
  int data;
  //指针
  int link;
};

//封装Element相关属性
struct StaticList {
  int maxSize;
  // elements[0]为附加头节点
  // int *p;
  struct Element *elements;
  // tail指针指向最后一个添加的元素,方便尾部插入
  int tail;
  // avail指针指向备用链表的第一个位置
  int avail;
  // 指示当前元素的个数
  int currentSize;
};

int initStaticList(struct StaticList *p, int maxSize) {
  p->avail = 1;
  p->tail = 0;
  p->currentSize = 0;
  p->maxSize = maxSize > 10 ? (maxSize + 1) : 10;
  p->elements = (struct Element *)malloc(p->maxSize * sizeof(struct Element));
  for (int i = 1; i < p->maxSize; i++) {
    p->elements[i].link = i + 1;
  }
  p->elements[0].link = -1;
  p->elements[maxSize].link = -1;
}

void addElement(struct StaticList *p, int x) {
  int cur = p->avail;
  if (cur == -1) {
    printf("memory overflow");
    return;
  }
  p->avail = p->elements[cur].link;
  p->elements[cur].data = x;
  //新插入元素指针域为-1
  p->elements[cur].link = -1;
  p->elements[p->tail].link = cur;
  p->tail = cur;
  p->currentSize++;
}
void output(struct Element *ems) {
  int cur = ems[0].link;
  while (cur != -1) {
    printf("%d, ", ems[cur].data);
    cur = ems[cur].link;
  }
  printf("\n");
}

//静态链表插入排序,从小到大
void insertSortElement(struct Element ems[]) {
  int ptr = ems[0].link;
  if (ptr == -1 || ems[ptr].link == -1) {
    return;
  }
  ptr = ems[ptr].link;
  // last记录最大元素所指指针
  int last = ems[0].link;
  // pre,cur分别为内层循环的前后两个节点的指针
  int pre, cur;
  // ptr作为外层循环的指针,nextptr作为ptr的后继节点的指针
  int nextptr;
  while (ptr != -1) {
    pre = 0;
    cur = ems[0].link;
    //若要实现从大到小,则修改为ems[cur].data > ems[ptr].data
    while (ems[cur].data < ems[ptr].data) {
      pre = cur;
      cur = ems[cur].link;
    }
    //暂存ems[ptr].link指针,否则会被覆盖掉
    nextptr = ems[ptr].link;
    // cur指针没有指向ptr,说明有插入
    if (cur != ptr) {
      // ptr的后续节点指向cur
      ems[ptr].link = cur;
      // pre的后续节点指向ptr
      ems[pre].link = ptr;
      //更新最大元素的指针域指向最新节点,若nextptr为最后一个节点时取-1
      ems[last].link = nextptr;
    } else {
      //若元素已经有序,则当前记下当前指针ptr作为最大元素的指针
      last = ptr;
    }
    ptr = nextptr;
  }
}

int main() {
  struct StaticList p;
  int arr[] = {1, 4, 6, 7, 0, 3, 10, 9, 2, 9,
               3, 7, 4, 8, 5, 6, 1, 0, 9999, 44,
               9999, 2, 4, 9, 4, 12, 44, 99, -1, 0,
               5, 888, 345, 12344, 5555, 9090, 10000, 123, 444, -888,
               100, 400, 120000, 899999, -1, -2, 8, 9, 8};
  // int arr[]={6,4,3,8,7,5,2,1};
  // int arr[] = {2, 5, 6, 7, 1, 4, 8, 9};
  // int arr[] = {9, 0};
  int n = sizeof(arr) / sizeof(int);
  initStaticList(&p, n);
  for (int i = 0; i < n; i++) {
    addElement(&p, arr[i]);
  }
  printf("#### insert sort before ####\n");
  output(p.elements);
  printf("#### insert sort after ####\n");
  insertSortElement(p.elements);
  output(p.elements);
  free(p.elements);
  return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值