一、插入排序
1、基本思想
插入排序(以升序为例)的基本操作是将一个数插入到一个已经排好序的数据序列中,插入后的数据序列是有序的并且元素个数加一。插入排序的主要思想是:
假设要排序的数组为A[]元素个数为n,将这个数组分为两个部分前n-1个元素和最后一个元素,将最后一个元素插入到已经排好序的n-1个元素中的合适的位置。
InsertSort(A[n]) //对A[n]进行插入排序
{
for i=1 to n
divide(A[i-1],a[i]) //将A[i]分为两部分,前i-1个元素和最后一个元素
Insert(a[i],A[i-1])//将最后一个元素插入到排好序的前i-1个元素中
}
2、算法复杂度分析
插入排序存在着最好情况和最坏情况,最好的情况是已经是排好序的了,这时只需比较n-1次即可;最坏的情况是序列是降序的需要排成升序的,那么此时就需要比较n(n-1)/2。插入排序的赋值操作是比较操作的次数加上n-1次。平均来说插入排序的算法复杂度为O(n2)。
3、编程实现
public static void InsertSort(int[] A){
for(int i=1;i<A.length;i++){
int temp=A[i];//存储要插入的元素
int j=i;
//将temp与A[j-1]中的数进行比较,插入到合适的位置
while(j>0&&A[j-1]>=temp){
A[j]=A[j-1];
j--;
}
A[j]=temp;
}
}
二、冒泡排序
1、基本思想
冒泡排序(以升序为例)的基本思想是:依次比较相邻的两个数,将小数放在前面,大数放在后面,第一轮比较后,最大的数便被放到了最后;第二轮操作前n-1个数据(假设有n个数据),依然是依次比较相邻的两个数,将小数放在前面,大数放在后面,倒数第二个数便是第二大的数;同理第i轮操作前n-i+1的数据(假设i取值是从1开始的),则n-i+i位置上的数据为第i大的数据。一共有n-1轮,第i轮比较中共比较n-i次比较。
BubbleSort(A[n])
{
for(i=1;i<n;i++)
for(j=0;j<n-i;j++)
if(A[j]>A[j+1]) A[j]?A[j+1];//交换次序
}
2、算法复杂度分析
一共有 次比较,最好情况下没有赋值运算,而最坏的情况下有3次赋值运算,因此该算法复杂度为O(n2)。
3、编程实现
排序后的结果为升序
public static void BubbleSort(int[] A){
int temp=0;
for (int i = 1; i < A.length ; i++) {
for (int j = 0; j < A.length - i ; j++){
//将大数往后放
if (A[j]>A[j + 1]){
temp=A[j];
A[j]=A[j + 1];
A[j + 1]=temp;
}
}
}
}
4、改进的冒泡排序算法
若冒泡排序在某轮循环中已经排好了序,此时就不需要进行下一轮的循环。因此改进的方法是对在比较中是否发生数据交换进行标识。如设置一个布尔量flag,进行比较前将其值设置为true,若发生了数据交换则将flag设置为false,一轮比较完成后,判断flag的值为true则结束排序,否则进行下一轮的比较。
改进后的冒泡排序时间复杂度为O(n2)。
public static void BubbleSort(int[] A){
int temp=0;
for (int i = 1; i < A.length &&flag=false; i++) {
boolean flag=true;
for (int j = 0; j < A.length - i +1; j++){
//将大数往后放
if (A[j]>A[j + 1]){
temp=A[j];
A[j]=A[j + 1];
A[j + 1]=temp;
flag=false;
}
}
}
}
三、堆排序
1、基本思想
堆排序的基本思想是:首先将待排序的记录序列构造成一个堆,这时堆的头结点既是最大元素,将其和最后一个记录交换;然后将除了最后一个记录的记录序列再调整成堆,这样就找出了次大的元素,以此类推,直到堆中只有一个记录为止。
调整堆的思想是将待调整的结点的左右孩子进行比较选出较大的一个和待调整结点进行比较,若待调整结点小则交换,否则调整结束。
创建堆得思想是从第n个结点的父结点开始到第1个结点逐个扫描,每一次扫描将以当前结点为根的子树转换成堆。
HeapSort(A)
{
MakeHeap(A)//构造出一个堆
for j←n down to 2 do
Swap(A[1],A[j])//交换头结点和最后一个记录
Siftdown(A[1,……,int((j-1)/2)] ,1)//调整堆
end for
}
Siftdown(H,i)
{
if 2i>n then exit //若下到了叶子结点则退出
repeat i←2i
//寻找待下移结点的左右孩子中较大的一个
if i+1<=n and H[i+1]>H[i] then
i←i+1
end if
//若待下移结点比左右孩子中较大的一个小则交换
if H[int(i/2)]<H[i] then
Swap(H[int(i/2)],H[i])
else break //若待下移结点比左右孩子大则调整结束
end if
}
MakeHeap(A)
{ i←n //总共有n个元素
m←int(i/2)
for j←m down to 1 //从第n个元素的父结点开始到第1个元素
do Sift-down(A[m,m+1,……,n],i) //即父结点小于孩子结点则进行交换
end for
}
2、算法复杂度分析
堆排序的时间复杂度主要有构造堆和调整堆这两部分的时间复杂度构成,构造堆的时间复杂度在最坏的情况下是O(n),调整堆的时间复杂度为O(log2n)。因此堆排序的大致时间复杂度可以写成O(n)+(n-1)(1+O(log2n)),即时间复杂度为O(nlog2n)。
3、编程实现
public static void HeapSort(int[] A){
//MakeHeap(A)将数组A构建成堆
for(int i=A.length/2;i>0;i--)
Siftdown(A,i,A.length);//数组A,要下移的元素为A[i]
for(int i = A.length ; i >1 ; i--){
//交换头结点和最后一个元素
int temp=A[1];
A[1]=A[i];
A[i]=temp;
Shiftdown(A, 1,i-1);//调整堆
}
}
private static void shiftDown(int[] A, int i,int n){
while(2*i<n){
j=2*i;
//找出最大的孩子
if((j+1<n)&&A[j+1]>A[j])
j=j+1;
//待下移结点和最大的孩子进行比较,若小则下移否则调整结束
if(A[i]<A[j]){
int temp=H[i];
H[i]=H[j];
H[j]=temp;
}
else break;
}
}
四、合并排序
1、基本思想
合并排序的基本操作是:首先将待排序序列划分为两个长度相等的子序列;然后分别对两个子序列进行归并排序,得到两个有序的子序列;最后将两个有序的子序列合并成一个有序数列。
MergeSort(A[2*n])
{
divide A[2*n] into A[1,……,n],A[n-1,……,2*n];//划分
MergeSort(A[1,……,n]);//归并排序前半个子序列
MergeSort(A[[n-1,……,2*n]);//归并排序后半个子序列
Merge;//合并
}
2、算法复杂度分析
合并步的时间复杂度为O(n)。合并排序算法的时间复杂度为O(nlog2n)。
3、编程实现
public int[] MergeSort(int[] A, int[] tempA, int s, int t){
//如果序列中有一个以上的元素,即s<t则进行排序
if(s < t){
int center = (s + t) / 2;
MergeSort(A, tempA, s, center);//归并排序前半个子序列
MergeSort(A, tempA, center + 1, t);//归并排序后半个子序列
Merge(A,tempA, s, center, t);//合并
}
return tempA;
}
public int[] Merge(int[] A, int[] tempA, int s, int m, int t){
int n = t- s + 1;//n为数据总个数
int i=s;j=m+1;k=s
while(i <= m&& j <= t){
//取A[i]和A[j]中较小者放入tempA[k]
if(A[i]<=A[j]){
tempA[k++] = A[i++];
}
else{
tempA[k++] = A[j++];
}
}
if(i<=m)
while(i<=m)
tempA[k++]=A[i++];//处理前一个子序列
else while(j<=t)
tempA[k++]=A[j++];//处理后一个子序列
return tempA;
}
五、快速排序
1、基本思想
快速排序的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
QuickSort(A,first,end)
{
if first<end then
m=Partition(A,first,end)//将数据分割成独立的两部分
QuickSort(A,first,m-1)//递归的对左侧子序列进行快速排序
QuickSort(A,m+1t,end)//递归的对右侧子序列进行快速排序
end if
}
其中通过一趟排序将要排序的数据分割成独立的两部分的基本操作步骤如下:
1)设置两个变量i、j,排序开始的时候:i=fist,j=end;取第一个数组元素作为基准元素。
2)将基准元素和j指向的元素进行比较,如果j指向的元素大则j--;重复直到j指向的元素小并i<j,则将基准元素和j所指向的元素进行交换。
3)将基准元素和i指向的元素进行比较,如果i指向的元素小则i--;重复直到i指向的元素大并i<j,则将基准元素和i所指向的元素进行交换。
4)重复第2、3步,直到 i=j;
2、算法复杂度分析
在最好的情况下,每次划分后左侧子序列和右侧子序列的长度相同。在有n个元素的数组中,一次划分需要的时间是O(n),而每次划分后,正好为长度相等的两个子序列,因此有,因此时间复杂度为O(nlog2n)。
在最坏的情况下,待排序数组为升序或逆序排列,则每次划分只得到比上一次少一个元素的子序列(另一个为空)。此时必须经过n-1次递归调用,且第i次划分需要比较n-i次才能找到基准位置。因此时间复杂度为O(n2)
在平均情况下, ()。用归纳法证明知时间复杂度也为O(nlog2n)。
3、编程实现
public static void QuickSort(int A[],int first,int end){
if(first<end){
m=Partition(A,first,end);
QuickSort(A,first,m-1);
QuickSort(A,m+1,end);
}
}
public int Partition(int A[],int fist,int end){
int i=fist;j=end;
int temp;
while(i<j){
while(i<j&&A[i]<=A[j])j--;
if(i<j){
temp=A[i];A[i]=A[j];A[j]=temp;
i++;
}
while(i<j&&A[i]<=A[j])i++;
if(i<j){
temp=A[i];A[i]=A[j];A[j]=temp;
j--;
}
}
return i;
}