数据结构与排序算法—基本概念
基本概念
数据结构
数据结构:存储数据的不同方式或组织存储数据的集合,分为逻辑结构和物理结构
逻辑结构:集合结构、线性结构、树形结构、图形结构
物理结构:顺式结构、链式结构
算法
算法:同一问题的不同解决方法,算法往往是解决特定的数据结构
测算算法优劣:时间、 空间
如何写算法程序
由简单到复杂:验证一步走一步、多打印中间结果
先局部后整体:没思路时先细分
先粗糙后精细:变量更名、语句合并、边界处理
时间复杂度分析
-
算法随着输入规模增长量时(算法函数中规律):
- 常数可以忽略
- 最高次幂的常数因子可以忽略
- 最高次幂越小,算法效率越高 大O记法
- 用常数1取代所有运行时间中的所有加法常数
- 只保留最高阶项
- 去除最高阶项系数
空间复杂度分析
基本数据类型 | 内存占用字节数 |
---|---|
byte | 1 |
boolean | 1 |
char | 2 |
short | 2 |
int | 4 |
float | 4 |
long | 8 |
double | 8 |
1、计算机访问内存一次1字节
2、一次引用8个字节
3、创建对象16个字节
4、不足8个字节时,自动填充8个字节(20个字节填充为24个字节)
排序算法宋词记忆法
选泡插
快归堆希统计基
恩方恩老恩一三
对恩加 k 恩 乘 k
不稳稳稳不稳稳
不稳不稳稳稳稳
Comparable接口:Java提供的用来定义排序规则
算法
选择排序
思想:每遍历一次列表找到最小的数字,将最小数字和最左边交换,依次从左到右进行
动画展示:
最初版:
public class selectionsort {
public static void main(String[] args) {
int [] arr={3,44,38,5,47,15,36,26,27,2,46,4,19,50,48};
for(int i=0;i<arr.length;i++){
int minpos=i;
for (int j = i+1; j < arr.length; j++) {
if (arr[minpos]>arr[j]){
minpos=j;
}
}
int temp=arr[i];
arr[i]=arr[minpos];
arr[minpos]=temp;
}
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]+" ");
}
}
}
修改版:
public class selectionsort2 {
public static void main(String[] args) {
int[] arr={3,44,38,5,47,15,36,26,27,2,46,4,19,50,48};
for (int i = 0; i < arr.length-1; i++) {
int minpos=i;
for (int j = i+1; j < arr.length; j++) {
minpos=arr[j]<arr[minpos]?j:minpos;
}
System.out.print("minpos:"+minpos);
swap(arr, i, minpos);
System.out.println("经过第"+i+"次变换后,所得数组为:");
show(arr);
}
}
public static void swap(int[] arr,int i,int j){
int temp=arr[i];
arr[i]=arr[j];
arr[j]=temp;
}
public static void show(int[] arr){
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]+" ");
}
}
}
冒泡排序(常考)
思想:不断交换使大的数据后移,使最大数据归位
图形展示:
代码展示:
public class BubbleSort {
public static void main(String[] args) {
int[] arr={3,44,38,5,47,15,36,26,27,2,46,4,19,50,48};
sort(arr);
show(arr);
}
public static void sort(int[] arr){
for (int i = arr.length-1; i >0; i--) {
findMax(arr, i);
}
}
public static void findMax (int[] arr,int n) {
for (int j = 0; j < n; j++) {
if(arr[j]>arr[j+1]){
swap(arr, j, j+1);
}
}
}
public static void swap(int[] arr,int i,int j){
int temp=arr[i];
arr[i]=arr[j];
arr[j]=temp;
}
public static void show(int[] arr){
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]+" ");
}
}
}
插入排序(常考)
思想:从左往右依次把最小的数移到最前面。
图形展示:
代码展示:
public class InsertionSort {
public static void main(String[] args) {
int[] arr={3,44,38,5,47,15,36,26,27,2,46,4,19,50,48};
sort(arr);
show(arr);
}
public static void sort(int[] arr){
for (int i = 0; i < arr.length; i++) {
for (int j = i; j >0 && arr[j]<arr[j-1]; j--) {
swap(arr, j, j-1);
}
}
}
public static void swap(int[] arr,int i,int j){
int temp=arr[i];
arr[i]=arr[j];
arr[j]=temp;
}
public static void show(int[] arr){
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]+" ");
}
}
}
简单排序算法总结
冒泡:基本不用,太慢
选择:基本不用,不稳
插入:样本小且基本有序的时候效率高
希尔排序
思想:先给一个间隔,排序,间隔不断缩小排序,最后间隔为1
图形展示:
间隔为4
间隔为2,为1,最后间隔为1是用了插入排序:
knuth序列:
h=1
h=3*h+1
代码展示:
public class ShellSort {
public static void main(String[] args) {
int[] arr={9,6,11,3,5,12,8,7,10,15,14,4,1,13,2};
sort(arr);
show(arr);
}
public static void sort(int[] arr){
int h=1;
while(h<arr.length/3){
h=h*3+1;
}
for (int gap = h; gap >0; gap=(gap-1)/3) {
for (int i = gap; i < arr.length; i++) {
for (int j = i; j > gap-1 && arr[j]<arr[j-gap]; j-=gap) {
swap(arr, j, j-gap);
}
}
}
}
public static void swap(int[] arr,int i,int j){
int temp=arr[i];
arr[i]=arr[j];
arr[j]=temp;
}
public static void show(int[] arr){
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]+" ");
}
}
}
归并排序(常考)
思想:将已有序的子序列合并,得到完全有序的序列
图形展示:
代码展示:
public class Mergesort1 {
public static void main(String[] args) {
int[] arr={1,4,7,8,3,6,9};
sort(arr);
}
public static void sort(int[] arr){
Merge(arr);
}
public static void Merge (int[] arr) {
int mid=arr.length/2;
int[] temp=new int[arr.length];
int i=0;
int j=mid+1;
int k=0;
while(i<=mid &&j<arr.length){
temp[k++]=arr[i]<=arr[j]?arr[i++]:arr[j++];
}
while(i<=mid)temp[k++]=arr[i++];
while(j<arr.length)temp[k++]=arr[j++];
show(temp);
}
public static void show(int[] arr){
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]+" ");
}
}
}
进阶版(递归思想):
public class Mergesort2 {
public static void main(String[] args) {
int[] arr={1,4,7,8,3,6,9};
sort(arr,0,arr.length-1);
show(arr);
}
public static void sort(int[] arr,int left,int right){
if(left==right) return;
//分成两半
int mid=left+(right-left)/2;
//左边排序
sort(arr, left, mid);
//右边排序
sort(arr, mid+1, right);
Merge(arr,left,mid+1,right);
}
public static void Merge (int[] arr,int leftPtr,int rightPtr,int rightBound) {
int mid=rightPtr-1;
int[] temp=new int[rightBound-leftPtr+1];
int i=leftPtr;
int j=rightPtr;
int k=0;
while(i<=mid &&j<=rightBound){
temp[k++]=arr[i]<=arr[j]?arr[i++]:arr[j++];
}
while(i<=mid)temp[k++]=arr[i++];
while(j<=rightBound)temp[k++]=arr[j++];
for (int m = 0; m < temp.length; m++) {
arr[leftPtr+m]=temp[m];
}
}
public static void show(int[] arr){
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]+" ");
}
}
}
快速排序(常考)
思想:从数列中挑出一个元素,比它小的排在前面,比它大的排在后面
图形展示:
代码展示:
public class QuickSort {
public static void main(String[] args) {
int[] arr={7,3,2,8,1,9,5,4,6,10,6};
sort(arr,0,arr.length-1);
show(arr);
}
public static void sort(int[] arr,int leftbound,int rightbound){
if(leftbound>rightbound) return;
int mid=Partition(arr,leftbound,rightbound);
sort(arr, leftbound, mid-1);
sort(arr, mid+1, rightbound);
}
public static int Partition (int[] arr,int leftbound,int rightbound) {
int pivot=arr[rightbound];
int left=leftbound;
int right=rightbound-1;
while(left<=right){
while( left<=right && arr[left]<=pivot)left++;
while(left<=right && arr[right]>pivot)right--;
if(left<right) swap(arr, left, right);
}
swap(arr, left, rightbound);
return left;
}
public static void swap(int[] arr,int i,int j){
int temp=arr[i];
arr[i]=arr[j];
arr[j]=temp;
}
public static void show(int[] arr){
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]+" ");
}
}
}
计数排序
思想:统计各个数字出现的次数,如高考成绩排名
代码展示:
package sort;
import java.util.Arrays;
public class CountSort1 {
public static void main(String[] args) {
int[] arr={2,4,2,3,7,1,1,0,0,5,6,9,5,7,4,0,8,9};
int[] result=sort(arr);
System.out.println(Arrays.toString(result));
}
public static int[] sort(int[] arr){
int[] result=new int [arr.length];
int [] count=new int [10];
for (int i = 0; i < arr.length; i++) {
count[arr[i]]++;
}
System.out.println(Arrays.toString(count));
for (int i = 0, j=0; i < count.length; i++) {
while(count[i]-->0){
result[j++]=i;
}
}
System.out.println(Arrays.toString(result));
return result;
}
}
优化性能:
增加了所处位置的排名
import java.util.Arrays;
public class CountSort {
public static void main(String[] args) {
int[] arr={2,4,2,3,7,1,1,0,0,5,6,9,5,7,4,0,8,9};
int[] result=sort(arr);
System.out.println(Arrays.toString(result));
}
public static int[] sort(int[] arr){
int[] result=new int[arr.length];
int[] count=new int[10];
for(int i=0;i<arr.length;i++){
count[arr[i]]++;
}
System.out.println(Arrays.toString(count));
//增加了此处的排名
for (int i = 1; i < count.length; i++) {
count[i]=count[i]+count[i-1];
}
System.out.println(Arrays.toString(count));
for (int i = arr.length-1; i>0; i--) {
result[--count[arr[i]]]=arr[i];
}
return result;
}
}
基数排序
思想:数字从低位到高位依次排序
图形展示:
代码展示:
package sort;
import java.util.Arrays;
public class RadixSort1 {
public static void main(String[] args) {
int[] arr={421,240,115,532,305,430,1141};
int[] result=sort(arr);
System.out.println(Arrays.toString(result));
}
public static int [] sort(int[] arr){
int [] result=new int[arr.length];
int [] count=new int[10];
int h=getnum(arr);
for (int i = 0; i < h; i++) {
int division=(int)Math.pow(10, i);
System.out.println("按照的位数排"+division);
for (int j = 0; j < arr.length; j++) {
int num=arr[j]/division%10;
System.out.print(num+" ");
count[num]++;
}
System.out.println(Arrays.toString(count));
//计数排序
for (int m = 1; m < count.length; m++) {
count[m]=count[m]+count[m-1];
}
System.out.println(Arrays.toString(count));
for (int n = arr.length-1; n >=0; n--) {
int num=arr[n]/division%10;
result[--count[num]]=arr[n];
}
System.arraycopy(result, 0, arr, 0, arr.length);
Arrays.fill(count, 0);
}
return result;
}
//利用冒泡排序的方法找到最大值,并计算其位数。
public static int getnum(int[] arr){
findmax(arr, arr.length-1);
int h=arr[arr.length-1];
int k=0;
while(h>0){
h=h/10;
k++;
}
return k;
}
public static void findmax(int[] arr,int n) {
for (int i = 0; i < n; i++) {
if (arr[i]>arr[i+1]) {
swap(arr, i, i+1);
}
}
}
public static void swap(int[] arr,int i,int j){
int temp=arr[i];
arr[i]=arr[j];
arr[j]=temp;
}
}
总结排序算法(面试)
外部排序和内部排序有哪些?(面试)
内部排序:待排序记录存放在计算机随机存储器中(说简单点,就是内存)进行的排序过程。
外部排序:待排序记录的数量很大,以致于内存不能一次容纳全部记录,所以在排序过程中需要对外存进行访问的排序过程
内部排序:插入排序、快速排序、选择排序、归并排序、基数排序等
外部排序基本上由两个相对独立的阶段组成。首先,按可用内存大小,将外存上含n个记录的文件分成若干长度为l的子文件或段,依次读入内存并利用有效的内部排序方法对他们进行排序,并将排序后得到的有序子文件重新写入外存,通常称这些有序子文件为归并段;然后,对这些归并段进行逐趟归并,使归并段逐渐由小至大,直至得到整个有序文件为止。
排序算法的稳定性及其汇总:
同样值的个体之间,如果不因排序而改变相对次序,就是这个排序具有稳定性,否则没有。
不具有稳定的排序:选择,快速,堆,希尔
具有稳定性的排序:冒泡,插入,归并,一切桶排序思想;
选择排序:数组中找到最小的,移到最前面去,不稳定;
冒泡排序:从左到右,一直两两比较,小的在前面,大的在后面,相等时,不用换,稳定;
插入排序:从左往右依次把最小的数移到最前面,如果右边的数不比左边的小,就停止,稳定;
归并排序:将已有序的子序列合并,保证相同的时候,都是先放第一个的,再放第二个,稳定;
快速排序:小于等于放左边,大于放右边:比如67663,5,然后3就会和第一个6交换,不稳定;
小于在左边,大于在右边,等于在中间;比如55553,5,就会把第一个5和3交换,不稳定;
堆排序:不稳定,5446,第一个4就要和6交换;
排序算法 | 时间 | 空间 | 稳定 |
---|---|---|---|
选择 | O ( N 2 ) O(N^{2}) O(N2) | O ( 1 ) O(1) O(1) | 不稳定 |
冒泡 | O ( N 2 ) O(N^{2}) O(N2) | O ( 1 ) O(1) O(1) | 稳定 |
插入 | O ( N 2 ) O(N^{2}) O(N2) | O ( 1 ) O(1) O(1) | 稳定 |
归并 | O ( N ∗ L o g N ) O(N*{LogN}) O(N∗LogN) | O ( N ) O(N) O(N) | 稳定 |
快速排序(随机) | O ( N ∗ L o g N ) O(N*{LogN}) O(N∗LogN) | O ( L o g N ) O(LogN) O(LogN) | 不稳定 |
堆 | O ( N ∗ L o g N ) O(N*{LogN}) O(N∗LogN) | O ( 1 ) O(1) O(1) | 不稳定 |
常见的坑
1,归并排序的额外空间复杂度可以变成O(1) ,但是非常难,不需要掌握,有兴趣可以搜“归并排序内部缓存法”&on·。
2,“原地归并排序”的帖子都是垃圾,会让归并排序的时间复杂度变成O(N^{2})
3,快速排序可以做到稳定性问题,但是非常难,不需要掌握,可以搜“01stable sort”
4,所有的改进都不重要,因为目前没有找到时间复杂度O(N*logN),额外空间复杂度O(1),又稳定的排序。
5,有一道题目,是奇数放在数组左边,偶数放在数组右边,还要求原始的相对次序不变,碰到这个问题,可以怼面试官。
-
总结
- 最坏情况、最好情况、排序方式不用记
- 插入排序、归并排序、堆排序、快速排序需要重点掌握