C/C++版数据结构之排序算法
今天讨论下数据结构中的排序算法。
排序算法的相关知识:
(1)排序的概念:所谓排序就是要整理文件中的记录,使之按关键字递增(或递减)次序排列起来。
(2)稳定的排序方法:在待排序的文件中,若存在多个关键字相同的记录,经过排序后这些具有相同关键字的记录之间的相对次序保持不变,该排序方法是稳定的。相反,如果发生改变,这种排序方法不稳定。
(3)排序算法的分类(分为5类):插入排序、选择排序、交换排序、归并排序和分配排序。
(4)排序算法两个基本操作:<1>比较关键字的大小。
<2>改变指向记录的指针或移动记录本身。
具体的排序方法:
插入排序
<1>插入排序(Insertion Sort)的思想:每次将一个待排序的记录按其关键字大小插入到前面已经排好序的子记录中的适当位置,直到全部记录插入完成为止。
<2>常用的插入排序方法有直接插入排序和希尔排序。
(1)直接插入排序
<1>算法思路:把一个记录集(如一个数组)分成两部分,前半部分是有序区,后半部分是无序区;有序区一开始有一个元素r[0],无序区一开始是从r[1]到之后的所有元素;然后每次从无序区按顺序取一个元素r[i],拿到有序区中由后往前进行比较,每次比较时,有序区中比r[i]大的元素就往后移动一位,直到找到小于r[i]的元素,这时r[i]插到小元素的后面,则完成一趟直接插入排序。如此反复,从无序区不断取元素插入到有序区,直到无序区为空,则插入算法结束。
<2>算法演示:
//直接插入排序:
#include<iostream>
using namespace std;
void InsertSort(int r[],int n);
int main()
{
int r[]={24,1,56,2,14,58,15,89};
InsertSort(r,8);
for(int i=0;i<8;i++)
{
cout<<r[i]<<'\t';
}
cout<<endl;
return 0;
}
void InsertSort(int r[],int n)
{
for(int i=1;i<n;i++)
{
for(int j=i-1,s=r[i];s<r[j] && j>=0;j--)
{
r[j+1]=r[j];
}
r[j+1]=s;
}
}
(2)折半插入排序
<1>算法思路:我们看到在直接插入排序算法中,需要在有序区查找比r[i]的小的元素,然后插入到这个元素后面,但这里要注意这个元素是从无序区算第一个比r[i]小的元素。折半插入排序就是在有序区折半查找这个元素。
<2>算法演示:
//折半插入排序
#include<iostream>
using namespace std;
void BinInsertSort(int r[],int n);
int main()
{
int r[]={53,34,76,23,55,28,63,88,34,66};
BinInsertSort(r,10);
for(int i=0;i<10;i++)
{
cout<<r[i]<<"\t";
}
cout<<endl;
return 0;
}
void BinInsertSort(int r[],int n)
{
for(int i=1;i<n;i++)
{
int s=r[i];
int low=0;
int high=i-1;
while(low <= high)
{
int mid=(low+high)/2;
if(s < r[mid])
{
high=mid-1;
}
else
{
low=mid+1;
}
}
for(int j=i-1;j>=high+1;j--)
{
r[j+1]=r[j];
}
r[high+1]=s; //r[high+1]是要找的元素
}
}
(3)希尔排序(Shell Sort)
<1>算法思路:把整个记录近一个步长step(一般取记录长度的1/2),分成step个组,再分别对每个级进行直接插入排序;然后再把整个记录近一个新的步长(一般取step/2)分成step/2个组,再分别对每个组进行直接插入排序;如此不断的缩小步长,反复分组排序,直到步长等于1为此(步长为1则不可能再分组,1是元素之间距离的最小值)。可以看出,希尔排序实质是一种分组插入方法。
<2>算法演示:
//希尔排序:
#include<iostream>
using namespace std;
void ShellSort(int r[],int n);
int main()
{
int r[]={24,1,56,2,14,58,15,89};
ShellSort(r,8);
for(int i=0;i<8;i++)
{
cout<<r[i]<<'\t';
}
cout<<endl;
return 0;
}
void ShellSort(int r[],int n)
{
int step=n/2;
while(step >= 1)
{
for(int i=step;i<n;i+=step)
{
for(int j=i-step,s=r[i];s<r[j] && j>=0;j-=step)
{
r[j+step]=r[j];
}
r[j+step]=s;
}
step/=2;
}
}
选择排序
<1>选择排序的思想:每一趟从待排序的记录中选出关键字最小的记录,顺序放在已经排好的记录最后,直到全部记录排序完毕。
<2>常用的选择排序方法有直接选择排序和堆排序。
(1)直接选择排序
<1>算法思路:把待排序的n个元素看成一个有序区和一个无序区,开始的时候有序区为空,无序区包含了全部n个元素。排序的时候,每次从无序区中选择比较出其中最小一个元素放到有序区中。如此反复操作,无序区中每小一个元素,有序区中就多一个元素,直到无序区的所有元素都转到有序区中。
<2>算法演示:
//简单选择排序:
#include<iostream>
using namespace std;
void SelectSort(int r[],int n);
int main()
{
int r[]={53,34,76,23,55,28,63,88,34,66};
SelectSort(r,10);
for(int i=0;i<10;i++)
{
cout<<r[i]<<"\t";
}
cout<<endl;
return 0;
}
void SelectSort(int r[],int n)
{
for(int i=0;i<n-1;i++)
{
int small_loc=i;
for(int j=i+1;j<n;j++)
{
if(r[small_loc] > r[j])
{
small_loc=j;
}
}
if(small_loc != i)
{
int temp=r[i];
r[i]=r[small_loc];
r[small_loc]=temp;
}
}
}
(2)堆排序
<1>算法思路:大根堆二叉树中的非终端结点的元素值均大于它的左右孩子的值,因此知道堆的最大值是它的根结点。当根结点移出,则重新调整堆后,堆的次大值称为根结点,依次操作,可以得到堆的从大到小的有序序列。这个算法过程就是堆排序。
堆排序有一个建堆、筛选堆、调整堆的过程。
<2>算法演示:
//堆排序:
#include<iostream>
using namespace std;
void HeapAdjust(int r[],int i,int j);
void HeapSort(int r[],int n);
int main()
{
int r[]={53,34,76,23,55,28,63,88,34,66};
HeapSort(r,10);
for(int i=0;i<10;i++)
{
cout<<r[i]<<"\t";
}
cout<<endl;
return 0;
}
void HeapAdjust(int r[],int i,int j) //调整堆
{
int child=2*i;
int temp=r[i]; //temp临时存放根结点
while(child <= j) //沿大儿子向下调整
{
if(child<j && r[child+1]>r[child]) child++;
if(temp >= r[child]) break;
r[child/2]=r[child];
child=2*child;
}
r[child/2]=temp;
}
void HeapSort(int r[],int n) //建堆
{
for(int i=(n-1)/2;i>=0;--i)
{
HeapAdjust(r,i,n-1); //初始建堆
}
for(i=n-1;i>0;--i)
{
//将当前堆顶元素与当前堆尾元素互换
int temp=r[0];
r[0]=r[i];
r[i]=temp;
HeapAdjust(r,0,i-1); //将剩下的元素重新调整成堆
}
}
交换排序
<1>交换排序的思想:两两比较待排序记录的关键字,发现两个记录的次序相反时即进行交换,直到没有反序的记录为止。
<2>常用的交换排序方法有冒泡排序和快速排序。
(1)冒泡排序
<1>算法思路:通过相邻元素的值的大小比较,并交换值较大的(较小的)元素,使得元素从一端移到到另一端,就像水底冒出的气泡一样。
<2>算法演示:
//起泡法排序:
#include<iostream>
using namespace std;
#define N 5 //N为数的总个数
void BubbleSort(int r[],int n);
int main()
{
int i;
int a[N];
cout<<"请输入"<<N<<"个数字:";
for(i=0;i<N;i++)
{
cin>>a[i];
}
BubbleSort(a,N);
for(i=0;i<N;i++)
{
cout<<a[i]<<"\t";
}
cout<<endl;
return 0;
}
void BubbleSort(int r[],int n)
{
for(int i=0;i<n-1;i++) //进行n-1次循环,实现n-1趟比较
{
for(int j=0;j<n-1-i;j++) //在每一趟中进行n-1-i次比较
{
if(r[j]>r[j+1])
{
int temp=r[j];
r[j]=r[j+1];
r[j+1]=temp;
}
}
}
}
(2)快速排序
<1>算法思路:通过一趟排序将准备排序的元素集合分成两个部分,其中一部分的元素的值都小于另一部分,然后对这两部分的元素集合内部再分别重复进行上面的排序过程,直到所有的元素都排列有序。
<2>算法演示:
//快速排序:
#include<iostream>
using namespace std;
int Partition(int r[],int low,int high);
void QuickSort(int r[],int low,int high);
int main()
{
int r[]={53,34,76,23,55,28,63,88,34,66};
QuickSort(r,0,10-1);
for(int i=0;i<10;i++)
{
cout<<r[i]<<"\t";
}
cout<<endl;
return 0;
}
int Partition(int r[],int low,int high)
{
int pivotkey=r[low];
int i=low;
int j=high;
while(i<j)
{
while(i<j && r[j]>pivotkey) j--;
if(i<j){r[i]=r[j];i++;}
while(i<j && r[i]<pivotkey) i++;
if(i<j){r[j]=r[i];j--;}
}
r[j]=pivotkey;
return j;
}
void QuickSort(int r[],int low,int high)
{
if(low<high)
{
int pivot=Partition(r,low,high);
QuickSort(r,low,pivot-1);
QuickSort(r,pivot+1,high);
}
}
归并排序
<1>归并排序的思想:假设数组r有n个元素,那么可以看成数组r是由n个有序的子序列组成,每个子序列的长度为1,然后再两合并,得到了一个长度是2(或1)的有序子序列,再两两合并,如此重复,直到得到一个长度为n的有序数据序列为止,这种排序方法称为二路归并排序。
<2>常用的交换排序方法有二路归并排序和三路归并排序。
(1)二路归并排序
<1>算法思路:如上。
<2>算法演示:
//二路归并排序
#include <iostream>
using namespace std;
int *a=new int[20];
int n=0;
//归并排序,排序结果放到了b[]中
void Merge(int a[],int b[],int left ,int mid,int right)//此处的right指向数组中左后一个元素的位置
{
int i=left;
int j=mid+1;
int k=left;
while(i<=mid && j<=right)
{
if(a[i]<=a[j])b[k++]=a[i++];
else b[k++]=a[j++];
}
while(i<=mid) b[k++]=a[i++];
while(j<=right) b[k++]=a[j++];
}
//从b[]中又搬到了a[]中
void Copy(int a[],int b[],int left,int right)//right同上
{
for(int i=left;i<=right;i++)
a[i]=b[i];
}
//划分并排序
void MergeSort(int a[],int left,int right)//同上
{
int *b = new int[right-left+1];
if(left<right)
{
//将当前传经来的数组划分成更小的大小几乎相同的数组
int i=(left+right)/2;
MergeSort(a,left,i);
MergeSort(a,i+1,right);
//将小数组合成大数组,同时排序,结果放到b[]中
Merge(a,b,left,i,right);
//从b[]中挪到a[]中
Copy(a,b,left,right);
}
}
void Input()
{
cout<<"Please Input array's size:";
cin>>n;
cout<<"Array's elemants:"<<endl;
for(int i=0;i<n;i++)
cin>>a[i];
//调用算法
MergeSort(a,0,n-1);
}
void Output()
{
for(int i=0;i<n;i++)
cout<<a[i]<<" ";
cout<<endl;
}
int main()
{
Input();
Output();
return 0;
}
归并排序在笔试面试中考的比较多,下面是另一个版本:
#include <iostream> using namespace std; bool MergeSort(int a[], int n); void MergeSort(int a[], int first, int last, int *pArray); void MergeSortArray(int a[], int first, int mid, int last, int *pArray); int main() { int a[] = {72, 6, 57, 88, 60, 42, 83, 73, 48, 95}; if(MergeSort(a, sizeof(a) / sizeof(a[0]))) { for(int i = 0; i < sizeof(a) / sizeof(a[0]); i++) { cout<<a[i]<<"\t"; } cout<<endl; } return 0; } bool MergeSort(int a[], int n) { int *pArray = new int[n]; if(pArray == NULL) { return false; } MergeSort(a, 0, n-1, pArray); delete[] pArray; return true; } void MergeSort(int a[], int first, int last, int *pArray) { if(first < last) { int mid = (first + last) / 2; MergeSort(a, first, mid, pArray); //左边有序 MergeSort(a, mid + 1, last, pArray); //右边有序 MergeSortArray(a, 0, mid, last, pArray); //合并两个有序数组 } } void MergeSortArray(int a[], int first, int mid, int last, int *pArray) { int i = first; int j = mid + 1; int m = mid; int n = last; int k = 0; while((i <= m) && (j <= n)) { if(a[i] < a[j]) { pArray[k++] = a[i++]; } else { pArray[k++] = a[j++]; } } if(i <= m) { pArray[k++] = a[i++]; } if(j <= n) { pArray[k++] = a[j++]; } for(i = 0; i < k; i++) { a[i+first] = pArray[i]; } }
分配排序
这种方法笔试中用的少,故在此处略去。
最后祝大家元旦快乐!
再上传下哥们MoreWindows关于排序做的总结:MoreWindows白话经典算法之七大排序第2版