直接插入排序
思路
在排序中,序列为以下状态:
有序 | 待排 | 无序 |
---|---|---|
L[1,…,i-1] | L[i] | L[i+1,…,n] |
要将L[i]插入有序序列L[1,…,i-1]中
1,在L[1,…,i-1]中找到L[i]的插入位置K(从后向前遍历,K>=L[i])
2,将L[k,…,i-1]全部后移一位,空出L[k]
3,将L[i]插入L[K]
初始时,我们将L[1]看做有序,依次对L[2]-L[n]执行n-1次插入排序
代码
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
void print(int A[], int length)
{
for (int i = 0; i < length; ++i)
{
printf("%d ", A[i]);
}
}
//直接插入排序
void InsertSort(int A[], int n) {
int i, j, temp;
for (i = 1; i < n; ++i)//从第二个元素开始遍历
{
if (A[i] < A[i - 1]) {//若当前遍历的元素>=前一元素,已经有序故不操作;若<前一元素,则执行
temp = A[i];//暂存元素值
for (j = i - 1; j >= 0&&A[j]>temp; --j)//从前一元素,向前遍历有序序列,直到遇到大于等于该元素的,或者遍历到头
A[j + 1] = A[j];
A[j + 1] = temp;//插在A[J]后面(若A[j]==temp,保证了稳定性)
}
}
}
void test()
{
int A[10];
int length = sizeof(A) / sizeof(A[0]);
srand(time(0));
for (int i = 0; i < length; ++i)
{
A[i] = rand() % 100;//取0-99的随机数 0-99,都为原值,100-199为0-99,以此类推
}
printf("原始序列为:\n");
print(A, length);
InsertSort(A, length);
printf("\n排序后为:\n");
print(A, length);
}
int main()
{
test();
}
链表实现
思路
对数组插入排序时,是两重循环,第一重是从第二个元素开始遍历整个数组,第二个就是反向循环,从当前元素开始向前寻找第一个小于当前数的值,并插到它后面。
但单链表无法反向,所以第二重循环就可以进行正向遍历,找到第一个大于当前结点值的结点,并插到它前面。
借助两个指针sort和unsort,分别指向待排序的第一个元素,和未排序的最后一个元素,且unsort->next==sort。
1,当前元素大于链表第一个元素就直接头插法
2,当前元素不大于第一个元素,就从第二个元素开始遍历已排序的元素(利用p和q两个指针),要么已排序都小于当前元素,则插入尾部。要么找到大于当前元素的节点,插在该节点前即可。
错误点
1,unsort与sort对比是从第二个元素开始遍历,L->next->next开始
2,遍历要遍历完sort中最后一个元素,因为之前只和第一个元素对比过了。
代码
#include<stdio.h>
#include "malloc.h"
#include<stdlib.h>
#include<time.h>
//定义链表结构体
typedef struct LinkList
{
int data;
int id;//唯一标识每个节点
struct LinkList* next;
}LinkList;
//交换链表中两个数据
void swap(LinkList* p, LinkList* min)
{
int temp = p->data;
p->data = min->data;
min->data = temp;
};
void Insertsort(LinkList* L)
{
LinkList* sort, * unsort, * p, * q;
if (L != NULL) { //保证链表不为空
sort = L->next;//指向排序好部分的最后一个元素
while (sort->next != NULL)//遍历未排序的第一个元素
{
unsort = sort->next;//每次循环都指向未排序的第一个元素
if (unsort->data < L->next->data) { //未排序的元素小于链表第一个元素
sort->next = unsort->next;//头插法,把第一个未排序的插入表头
unsort->next = L->next;
L->next = unsort;
}
else
{
q = L->next;
p = q->next;//从第二个元素开始遍历,第一个在上面头插法对比过了
while (unsort->data >= p->data) {//直到找到p>unsort
if (p->id == unsort->id)//遍历到unsort就停止,若遍历到unsort前一个就会少对比sort的最后一个元素
break;
q = p;
p = p->next;
}
if (p->id == unsort->id) {//遍历到unsort前。说明未排序元素均大于已排序元素,插入队尾
sort = unsort;
}
else//未遍历到unsort前,找到了大于未排序元素的p,插入p前
{
sort->next = unsort->next;
unsort->next = p;
q->next = unsort;
}
}
}
}
}
//输出链表节点
void print(LinkList* L)
{
LinkList* p = L;
for (int i = 0; i < L->data; ++i)//L为头结点不能动 否则每次遍历就会改变条件
{
printf("%d-%d ", p->next->id, p->next->data);
p = p->next;
}
}
void test()
{
//建立链表
LinkList* L = (LinkList*)malloc(sizeof(LinkList));
if (L == NULL) {
printf("内存分配不成功!\n");
}
else
{
L->data = 10;
LinkList* p = L;
srand(time(0));
for (int i = 0; i < L->data; ++i)
{
p->next = (LinkList*)malloc(sizeof(LinkList));
if (p->next) {
p = p->next;
p->data = rand() % 100;//取0-99的随机数 0-99,都为原值,100-199为0-99,以此类推
p->id = i;
}
}
p->next = NULL;
printf("原始序列为:\n");
print(L);
Insertsort(L);
printf("\n排序后为:\n");
print(L);
}
}
int main()
{
test();
}
折半插入排序
思路
在直接插入排序的基础上做改进,以减少比较次数。
有序 | 待排 | 无序 |
---|---|---|
L[1,…,i-1] | L[i] | L[i+1,…,n] |
1,对已排序列L[1,…,i-1]使用折半查找法找到比待排元素L[i]大的元素L[k]
2,将L[k,…,i-1]全部后移一位,空出L[k]
3,将L[i]插入L[K]
代码
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
void print(int A[], int length)
{
for (int i = 0; i < length; ++i)
{
printf("%d ", A[i]);
}
}
//折半插入排序
void InsertSort(int A[], int n) {
int i, j, low,high,mid,temp;
for (i = 1; i < n; ++i)//从第二个元素开始遍历
{
temp = A[i];//暂存数据
low = 0; high = i - 1;//已排序序列首尾元素
while (low <= high) {//折半查找
mid = (low + high) / 2;//取中间元素
if (A[mid] > temp)//不断缩小查找访问
high = mid - 1;
else
low = mid + 1;//low会不断指向值更大的方向
}
若j<low,待排元素比前面所有元素都大,无需后移,直接插在原处。
for (j = i - 1; j >= low; --j)//值大于temp的元素,依次后移
A[j + 1] = A[j];
A[low] = temp;//插入low位置
}
}
void test()
{
int A[10];
int length = sizeof(A) / sizeof(A[0]);
srand(time(0));
for (int i = 0; i < length; ++i)
{
A[i] = rand() % 100;//取0-99的随机数 0-99,都为原值,100-199为0-99,以此类推
}
printf("原始序列为:\n");
print(A, length);
InsertSort(A, length);
printf("\n排序后为:\n");
print(A, length);
}
int main()
{
test();
}
希尔排序
思路
插入排序适合基本有序的序列,若序列逆序时间复杂度达到
O
(
n
2
)
O(n^{2})
O(n2),若为正序则可提升到O(n),希尔排序(缩小增量排序)就是在此基础上进行优化。
首先确定一个小于n的增量d,将序列分为形如L[i,i+d,…,i+kd]的d个子表。即间隔d的元素组成子表,然后对每个子表进行插入排序。不断缩小d的值,进行排序。当整个表已经基本有序(具有较好的局部有序性)的时候(d=1),最后进行一次排序即可。
代码
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
void print(int A[], int length)
{
for (int i = 0; i < length; ++i)
{
printf("%d ", A[i]);
}
}
//希尔排序
void ShellSort(int A[], int n) {
int d, i, j, k, temp;
for (d = n / 2; d >= 1; d = d / 2)//取增量,以增量为间隔构建子表
{
for (i = 0; i < d; ++i)//遍历子表的头结点
{
for (j = i + d; j < n; j = j + d)//遍历每个子表的元素,默认第一个元素有序,按直接排序处理。
{
if (A[j] < A[j - d]) {
temp = A[j];//暂存数据
for (k = j - d; k >= 0 && temp < A[k]; k -= d)
A[k + d] = A[k];
A[k + d] = temp;
}
}
}//每次都使整个序列更有序,从而直接排序速度越来越快。
}
}
void test()
{
int A[10];
int length = sizeof(A) / sizeof(A[0]);
srand(time(0));
for (int i = 0; i < length; ++i)
{
A[i] = rand() % 100;//取0-99的随机数 0-99,都为原值,100-199为0-99,以此类推
}
printf("原始序列为:\n");
print(A, length);
ShellSort(A, length);
printf("\n排序后为:\n");
print(A, length);
}
int main()
{
test();
}