算法(一)排序
一般算法题的解题场景思路:排序、递归分治、贪心、动态规划、回溯法、暴力模拟
排序:
1.冒泡排序:时间复杂度O(
)、空间复杂度O(1)、稳定排序
2.选择排序:时间复杂度O()、空间复杂度O(1)、稳定排序
3.插入排序:时间复杂度O()、空间复杂度O(1)、稳定排序
4.快速排序:时间复杂度O(nlogn)、空间复杂度O(1)、非稳定排序
5.堆排序:时间复杂度O(nlogn)、空间复杂度O(1)、非稳定排序
6.归并排序:时间复杂度O(nlogn)、空间复杂度O(n)、稳定排序
7.基数排序:时间复杂度O(n)、空间复杂度O(n)
一般应用场景:根据时间复杂度和空间复杂度、稳定排序分析出的。
a.对于大量数据排序,很显然使用O(nlogn)的快排、堆排、归并会更快。
b.对于部分有序的数据排序,很显然使用冒泡、插入、快排比较合适。
c.根据稳定性需要选择对应的排序算法。
1.冒泡排序:时间复杂度O(
)、空间复杂度O(1)、稳定排序
(1)思想:以从小到大为例,将第i个元素与第i+1个元素进行比较,将较大元素放在i+1位上,之后比较i+1与i+2直到结束,每趟循环确定出了最大的元素,下一趟循环比较的范围缩小。
(2)实现:
void sort(int[] arr){
for(int i=0;i<arr.length;i++){
boolean isSwaped=false;//该趟循环中是否交换
//每趟循环从0下标开始,比较j与j+1元素,每趟确定一个最大元素,范围缩小
for(int j=0;j<arr.length-i-1;j++){
//从小到大:将大元素排在后
if(arr[j]>arr[j+1]){
swap(arr,j+1,j);
isSwaped=true;
}
//优化:如果某一趟循环不产生任何交换,则表明已经有序
if(!isSwaped){
return;
}
}
}
}
void swap(int[] arr,int i,int j){
int t=arr[i];
arr[i]=arr[j];
arr[j]=t;
}2.选择排序:时间复杂度O(
)、空间复杂度O(1)、稳定排序
(1)思想:以从小到大为例,第i个元素是第i小,前面i-1个元素已经有序,那么每趟循环从后面元素中选择出最小的元素,放在第i个元素上,每趟循环确定一个最小元素。
(2)实现:
void sort(int[] arr){
for(int i=0;i<arr.length;i++){
int temp=i;//该趟循环中最小元素下标
//从第i+1开始找最小元素
for(int j=i+1;j<arr.length;j++){
if(arr[temp]>arr[j]){
temp=j;
}
}
swap(arr,i,temp);//交换元素,将第i小元素放在第i位
}
}
void swap(int[] arr,int i,int j){
int t=arr[i];
arr[i]=arr[j];
arr[j]=t;
}3.插入排序:时间复杂度O(
)、空间复杂度O(1)、稳定排序
(1)思想:以从小到大为例,第一个元素默认有序,i从1开始,将第i个元素往前插入,如果前一个元素大,将前一个元素后移,如果前一个元素小,则将第i个元素插入该位置结束。
(2)实现:
void sort(int[] arr){
//当前需要插入的元素是第i个
for(int i=1;i<arr.length;i++){
int temp=arr[i];//临时保存第i个元素
int j=i-1;
while(j>=0&&arr[j]>temp){
arr[j+1]=arr[j];//第j位元素后移
j--;
}
arr[j+1]=temp;//插入第i个元素
}
}4.快速排序:时间复杂度O(nlogn)、空间复杂度O(1)、非稳定排序
(1)思想:以从小到大为例,每趟循环以当前数组最左元素为中枢元素,i从前开始找比中枢元素大的第一个元素,j从后开始找比中枢元素小的第一个元素,如果i<j则交换该两个元素,继续寻找,直到i>=j交换完毕,此时,i元素是该中枢元素的正确位置,将中枢元素与该元素交换,该位置之前的元素比中枢元素都小,该位置之后的元素比中枢元素都大,然后从中枢元素正确位置为界,将数组分成两部分,继续快速排序。
(2)实现:
void quickSort(int[] arr,int left,int right){
if(left<right){
int mid=fun(arr,left,right);//确定中枢元素的正确位置
quickSort(arr,left,mid-1);
quickSort(arr,mid+1,right);
}
}
int fun(int[] arr,int left,int right){
int temp=arr[left];//保存中枢元素
int i=left+1,j=right;
while(true){
//往后找第一个比temp大的元素
while(i<=j&&arr[i]<=temp){
i++;
}
//往前找第一个比temp小的元素
while(i<=j&&arr[j]>=temp){
j--;
}
if(i>=j){
break;
}
swap(arr,i,j);
}
//中枢元素归位,返回中枢元素正确位置
arr[left]=arr[j];
arr[j]=temp;
return j;
}
void swap(int[] arr,int i,int j){
int t=arr[i];
arr[i]=arr[j];
arr[j]=t;
}5.堆排序:时间复杂度O(nlogn)、空间复杂度O(1)、非稳定排序
(1)思想:以从小到大为例,将第n/2-1~0个元素循环下沉操作确定一个大顶堆,然后将循环将第0个元素与最后一个元素交换,将最大的元素放在堆末,下次交换的元素范围减1,对堆顶元素重新下沉,再重新交换。
(2)实现:
void sort(int[] arr){
int n=arr.length;
//从中间n/2-1开始往前进行下沉操作
for(int i = n/2-1;i>=0;i--){
downAdjust(arr,i,n);
}
//从最后一个元素开始确定当前的最大元素,交换后重新下沉
for(int i=arr.length-1;i>0;i--){
swap(arr,0,i);
downAdjust(arr,0,i);
}
}
void downAdjust(int[] arr,int k,int end){
int temp=arr[k];//保存下沉元素
int child=2*k+1;//确定左孩子元素位置
while(child<end){
//如果右孩子存在且右孩子更大,则如果交换应该交换右孩子
if(child+1<end&&arr[child+1]>arr[child]){
child++;
}
//如果父元素小于子元素,则将子元素上升,否则终止
if(temp<arr[child]){
arr[k]=arr[child];//大元素上升,替换即可,交换无意义
k=child;//接下来对该元素的子元素的子元素进行下沉
child=2*k+1;
}else{
break;
}
}
//将元素放置在对应位置
arr[k]=temp;
}
void swap(int[] arr,int i,int j){
int t=arr[i];
arr[i]=arr[j];
arr[j]=t;
}6.归并排序:时间复杂度O(nlogn)、空间复杂度O(n)、稳定排序
(1)思想:将数组分成两部分,将两部分分别进行归并排序,在进行合并,合并过程,要将两个数组的元素进行比较,需要用临时数组将合并的元素存储起来,然后在将临时数组放回原数组完成该部分排序。
void sort(int[] arr) {
mergeSort(arr,0,arr.length-1);
}
void mergeSort(int[] arr,int left,int right){
if(left<right){
int mid=(left+right)/2;//分数组
mergeSort(arr,left,mid);
mergeSort(arr,mid+1,right);
merge(arr,left,mid,right);//合并
}
}
void merge(int[] arr,int left,int mid,int right){
int[] temp=new int[right-left+1];//临时数组
int i=left,j=mid+1;//两部分数组的下标指针
int k=0;//临时数组的下标指针
while(i<=mid&&j<=right){
//放较小元素
if(arr[i]<=arr[j]){
temp[k++]=arr[i++];
}else{
temp[k++]=arr[j++];
}
}
//如果某个数组没有元素了,另一个有元素,则无法用比较进行放置
while(i<=mid){
temp[k++]=arr[i++];
}
while(j<=right){
temp[k++]=arr[j++];
}
//将临时数组元素放回去
for(int m=0;m<k;m++){
arr[left+m]=temp[m];
}
}7.基数排序:时间复杂度O(n)、空间复杂度O(n)
(1)思想:如果一个数组内的元素大小范围在0~n之内,则建立一个大小为n的数组,对原数组进行遍历计数,在遍历新数组,将计数的值放回到原数组中。
(2)实现:
void sort(int[] arr,int n) {
int[] temp=new int[n+1];//临时数组
for(int i=0;i<arr.length;i++){
temp[arr[i]]++;//统计为arr[i]的个数
}
int j=0;
for(int i=0;i<temp.length;i++){
//遍历i出现的次数放置元素
while(temp[i]!=0) {
arr[j++]=i;
temp[i]--;
}
}
}
