排序算法的稳定性简单定义为:如果Ai = Aj,排序前Ai在Aj前,排序后Ai还在Aj前,则称这种排序算法是稳定的。即保证排序前两个相等的数相对顺序不变。
排序算法的稳定性是由具体算法定的,在某种条件下,稳定的可变不稳定,不稳定也可变稳定
比如冒泡排序,如果把交换条件加一个等号,就会从稳定的排序变成不稳定的排序
入门排序必备,当年领我们入门的学长还把冒泡排序和选择排序搞混了=。=
依次比较相邻两个元素,如果顺序错了就调换一下,直到没有元素需要交换
最差时间复杂度:O(n^2)
最优时间复杂度:O(n)
平均时间复杂度:O(n^2)
稳定性:稳定
//基础版
var
n :longint;
i,j :longint;
a :array[0..10010] of longint;
procedure swap(var a,b:longint);
var
c:longint;
begin
c:=a; a:=b; b:=c;
end;
begin
read(n);
for i:=1 to n do read(a[i]);
for i:=1 to n do
for j:=1 to n-i do
if a[j]>a[j+1] then swap(a[j],a[j+1]);
for i:=1 to n do write(a[i], ' ');writeln;
end.
冒泡排序有很多细节优化
优化一:排序完成就退出排序,设置一个flag判断是否已经有序
var
n,len :longint;
i :longint;
a :array[0..10010] of longint;
flag :boolean;
procedure swap(var a,b:longint);
var
c:longint;
begin
c:=a; a:=b; b:=c;
end;
begin
read(n);
for i:=1 to n do read(a[i]);
flag:=true; len:=n;
while flag do
begin
flag:=false;
for i:=1 to len-1 do
if a[i]>a[i+1] then
begin
swap(a[i],a[i+1]);
flag:=true;
end;
dec(len);
end;
for i:=1 to n do write(a[i], ' ');writeln;
end.
优化二:优化一保证了循环次数最优,但是对于有序部分仍需要比较。那么就可以在优化一的基础上设置一个边界,减少比较次数
var
i :longint;
flag,n,len :longint;
a :array[0..10010] of longint;
procedure swap(var a,b:longint);
var
c:longint;
begin
c:=a; a:=b; b:=c;
end;
begin
read(n);
for i:=1 to n do read(a[i]);
len:=n; flag:=n;
while flag>0 do
begin
flag:=0;
for i:=1 to len-1 do
if a[i]>a[i+1] then
begin
swap(a[i],a[i+1]);
flag:=i+1;
end;
len:=flag;
end;
for i:=1 to n do write(a[i],' ');writeln;
end.
优化三:定向冒泡排序,又名鸡尾酒排序,排序方法是从低到高再从高到低(冒泡排序只是从低到高比较每个元素)
var
n,l,r :longint;
i :longint;
a :array[0..10010] of longint;
procedure swap(var a,b:longint);
var
c:longint;
begin
c:=a; a:=b; b:=c;
end;
begin
read(n);
for i:=1 to n do read(a[i]);
l:=1; r:=n;
while (la[i+1] then swap(a[i],a[i+1]);
dec(r);
for i:=r downto l+1 do
if a[i]
但是,乱序下,不管怎么优化,冒泡排序的效率依旧很差劲눈_눈
简单直观:每次找最小(大)的放到首(尾),再在剩余未排序的元素中继续找最小(大)
最差时间复杂度:O(n^2)
最优时间复杂度:O(n^2)
平均时间复杂度:O(n^2)
稳定性:不稳定
var
n,min,tt :longint;
i,j :longint;
a :array[0..10010] of longint;
procedure swap(var a,b:longint);
var
c:longint;
begin
c:=a; a:=b; b:=c;
end;
begin
read(n);
for i:=1 to n do read(a[i]);
for i:=1 to n-1 do
begin
min:=maxlongint;
for j:=i to n do
if a[j]maxlongint then swap(a[tt],a[i]); //可能打乱稳定性,所以选择排序是不稳定的
end;
for i:=1 to n do write(a[i],' ');writeln;
end.
快排利用的是分治策略:选出一个基准,把比基准小的放基准前,把比基准小的放基准后面,对每个分区递归进行上述操作。
最差时间复杂度:O(n^2)(每次选的基准都是最小或最大的元素,需要进行n-1次划分)
最优时间复杂度:O(nlogn) (每次选的基准都是中位数,只需要logn次划分)
平均时间复杂度:O(nlogn)
稳定性:不稳定
var
n :longint;
i :longint;
a :array[0..10010] of longint;
procedure swap(var a,b:longint);
var
c:longint;
begin
c:=a; a:=b; b:=c;
end;
procedure qs(l,r:longint);
var
x:longint;
i,j:longint;
begin
i:=l; j:=r; x:=a[(l+r)>>1];
while (i<=j) do
begin
while a[i]x do dec(j);
if (i<=j) then
begin
swap(a[i],a[j]);
inc(i); dec(j);
end;
end;
if il then qs(l,j);
end;
begin
read(n);
for i:=1 to n do read(a[i]);
qs(1,n);
for i:=1 to n do write(a[i], ' ');writeln;
end.
归并排序的实现分为递归实现(分治)和非递归实现(合并)
最差时间复杂度:O(nlogn)
最优时间复杂度:O(nlogn)
平均时间复杂度:O(nlogn)
稳定性:稳定
var
i :longint;
n :longint;
a,c :array[0..10010] of longint;
procedure merge_sort(l,r,mid:longint);
var
i,j,t:longint;
begin
i:=l; j:=mid+1; t:=l;
while (t<=r) do
begin
if (i<=mid) and ((a[i]<=a[j]) or (j>r)) then
begin
c[t]:=a[i];
inc(i);
inc(t);
end else
begin
c[t]:=a[j];
inc(j);
inc(t);
end;
end;
for i:=l to r do a[i]:=c[i];
end;
procedure merge(l,r:longint);
var
mid:longint;
begin
if l=r then exit;
mid:=(l+r)>>1;
merge(l,mid);
merge(mid+1,r);
merge_sort(l,r,mid);
end;
begin
read(n);
for i:=1 to n do read(a[i]);
merge(1,n);
for i:=1 to n do write(a[i],' ');writeln;
end.
最差时间复杂度:O(nlogn)
最优时间复杂度:O(nlogn)
平均时间复杂度:O(nlogn)
稳定性:不稳定
var
n,x,size :longint;
i :longint;
heap :array[0..10010] of longint;
procedure swap(var a,b:longint);
var
c:longint;
begin
c:=a; a:=b; b:=c;
end;
procedure heap_down(x:longint);
var
t:longint;
begin
while ((x<<1)<=size) do
begin
if heap[x<<1]t then
begin
swap(heap[x],heap[t]);
x:=t;
end else break;
end;
end;
procedure heap_up(x:longint);
begin
if x=1 then exit;
while x<>1 do
begin
if heap[x]>1] then
begin
swap(heap[x],heap[x>>1]);
x:=x>>1;
end else break;
end;
end;
begin
read(n); size:=0;
for i:=1 to n do
begin
read(x);
inc(size);
heap[size]:=x;
heap_up(size);
end;
while (size>0) do
begin
write(heap[1],' ');
heap[1]:=heap[size];
dec(size);
heap_down(1);
end;
end.
插入排序的原理类似于我们玩扑克牌的时候理牌或者站队的时候老师按大小个排队时给同学调整位置
对于未排序序列,在排好的序列中从后往前扫,找到正确位置插入
从第二个元素开始(默认第一个元素已有序)作为新元素,从后向前扫有序序列,如果有序数列中的元素大于新元素,则将它向后移一个位置,直到在有序数列中找到小于或等于新元素的,把新元素插入到它后面。
最差时间复杂度:O(n^2)(原数列降序排列)
最优时间复杂度:O(n) (原数列升序排列)
平均时间复杂度:O(n^2)
稳定性:稳定
优化一:常用二分查找优化比较次数,称为二分插入排序
最差时间复杂度:O(n^2)
最优时间复杂度:O(nlogn)
平均时间复杂度:O(n^2)
稳定性:稳定
var
n,t,l,r,mid :longint;
i,j :longint;
a :array[0..10010] of longint;
begin
read(n);
for i:=1 to n do read(a[i]);
for i:=2 to n do
begin
l:=1; r:=i-1; t:=a[i];
while (l<=r) do
begin
mid:=(l+r)>>1;
if a[mid]>t then r:=mid-1 else l:=mid+1;
end;
for j:=i-1 downto l do a[j+1]:=a[j];
a[l]:=t;
end;
for i:=1 to n do write(a[i],' ');writeln;
end.
优化二:希尔排序(shell sort),又名递减增量排序
希尔排序取一个步长,这样比较交换时可以让元素一次性朝最终位置前进一大步(优化插入排序每次只移一步),再取越来越小的步长,直到最后一步又变成普通的插入排序,但此时序列已接近升序
最差时间复杂度:O(n(logn)^2)
最优时间复杂度:O(n)
平均时间复杂度:取决于所取步长
稳定性:不稳定(在各自排序中相等顺序可能打乱)
var
n,tt,t :longint;
i,j :longint;
a :array[0..10010] of longint;
begin
read(n);
for i:=1 to n do read(a[i]);
tt:=0;
while tt<=n do tt:=tt*3+1; //确定初始步长
while (tt>=1) do
begin
for i:=tt+1 to n do
begin
j:=i-tt; t:=a[i];
while (j>0) and (a[j]>t) do
begin
a[j+tt]:=a[j];
j:=j-tt;
end;
a[j+tt]:=t;
end;
tt:=(tt-1) div 3;
end;
for i:=1 to n do write(a[i], ' ');writeln;
end.