数据结构
1. 数组和链表的区别
数组 | 链表 |
---|---|
长度固定 | 长度可变 |
在内存里的地址是连续的 | 在内存里的地址不一定连续 |
查询效率高 | 增删效率高 |
2. 冒泡排序
关于排序算法:
1:如果有n个数进行排序,那么需要比较 ( n 2 − n n^2-n n2−n)/2 次。
2:若一个排序算法不会改变两个相同数值的顺序,则该算法是稳定的。
为了方便说明,下面叙述中的1 2 3代表的不是具体数字,而是下标位置。
冒泡排序的过程:
假如有n个数。
则从1开始,1和2比,若1大于2,则交换,若小于,则不变。
然后2和3比,重复上述步骤。
然后3和4比,4和5比…直到 n-1和n比。
一轮下来,第n个位置的数肯定是最大数。也就是每一轮能排好一个最大数。
重复n-1轮,即完成了冒泡排序。
代码实现:
public static void bubbleSort(int []arr) {
for(int i =1;i<arr.length;i++) {
for(int j=0;j<arr.length-i;j++) {
if(arr[j]>arr[j+1]) {
int temp = arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}
}
}
}
冒泡排序总的平均时间复杂度为 O( n 2 n^2 n2)。
完整代码:
public class Test {
public static void main(String[] args) {
int arr1[] = {7,4,8,3,9,1,2,6,5};
bubbleSort(arr1);
for(int i=0;i<arr1.length;i++){
System.out.print(arr1[i]+"\t");
}
}
public static void bubbleSort(int []arr) {
for(int i =1;i<arr.length;i++) {
for(int j=0;j<arr.length-i;j++) {
if(arr[j]>arr[j+1]) {
int temp = arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}
}
}
}
}
3. 快速排序
(1)首先设定一个分界值,通过该分界值将数组分成左右两部分。
(2)将大于或等于分界值的数据集中到数组右边,小于分界值的数据集中到数组的左边。此时,左边部分中各元素都小于或等于分界值,而右边部分中各元素都大于或等于分界值。分界值的位置固定。
(3)然后,左边和右边的数据可以独立排序。对于左侧的数组数据,又可以取一个分界值,将该部分数据分成左右两部分,同样在左边放置较小值,右边放置较大值。右侧的数组数据也可以做类似处理。
(4)重复上述过程,可以看出,这是一个递归定义。通过递归将左侧部分排好序后,再递归排好右侧部分的顺序。当左、右两个部分各数据排序完成后,整个数组的排序也就完成了。
演示图:
代码实现:
public class Test {
public static void quickSort(int[] arr,int low,int high){
int i,j,temp,t;
if(low>high){
return;
}
i=low;
j=high;
//temp就是基准位
temp = arr[low];
while (i<j) {
//先看右边,依次往左递减
while (temp<=arr[j]&&i<j) {
j--;
}
//再看左边,依次往右递增
while (temp>=arr[i]&&i<j) {
i++;
}
//如果满足条件则交换
if (i<j) {
t = arr[j];
arr[j] = arr[i];
arr[i] = t;
}
}
//最后将基准为与i和j相等位置的数字交换
arr[low] = arr[i];
arr[i] = temp;
//递归调用左半数组
quickSort(arr, low, j-1);
//递归调用右半数组
quickSort(arr, j+1, high);
}
public static void main(String[] args){
int[] arr = {10,7,2,4,7,62,3,4,2,1,8,9,19};
quickSort(arr, 0, arr.length-1);
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]+"\t");
}
}
}
快速排序总的平均时间复杂度为 O( n l o g n nlogn nlogn)。
4. 插入排序
首先从第二个数字开始,与前面的数字比较一次。
再从第三个数字开始,分别与前两个数字比较,比较两次。
再从第四个数字开始,分别与前三个数字比较,比较三次。
直到第n个数字比较,与前面n-1个数字比较n-1次,完成插入排序。
图示:
代码实现:
public class Test {
static void InsertSort(int[] a) {
for (int i = 1; i < a.length; i++) {
for (int j = i; j > 0; j--) {
if (a[j] < a[j - 1]) {
swap(a, j, j - 1);
}
}
}
}
static void swap( int[] a, int i, int j){
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
static void print ( int[] arr){
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + "\t");
}
}
public static void main (String[] args){
int[] a = {10, 7, 2, 4, 7, 62, 3, 4, 2, 1, 8, 9, 19};
InsertSort(a);
print(a);
}
}
插入排序总的平均时间复杂度为 O( n 2 n^2 n2)。
5. 希尔排序
首先判断一个序列的个数n。
然后取n/2=d。若n为奇数,则d取整。
然后将1和d+1比较,2和d+2比较。直到比到n-d和n比较。 一轮循环完毕。
第二轮:d/2=d1,若d为奇数,则d1取整。
然后将1和d1+1比,2和d2+2比较。直到比到n-d1和n比较。 二轮循环完毕。
第三轮:d1继续除2取整,继续上面的循环。
直到d(n)为1时,两两比较:
若1 2 不变,则2 3比,2 3不变,则3 4 比。直到最后即可。
若 1 2不变,则2 3比,2 3交换,则1 2回溯,需再比一次。然后才能 3 4比。
核心思想:最后一次需要两两比较的时候,若i和i+1交换了位置,则新的i需要回溯,即新的i和i-1比,若i和i-1也发生交换,则继续回溯。
如此循环,直到最后。
希尔排序属于高效率的插入排序。
bilibili视频演示动画–>数据结构排序算法之希尔排序演示
代码实现:
public class Test {
public static void main(String[] args){
int[] array={49,38,65,97,76,13,27,49,78,34,12,64,1};
System.out.println("排序之前:");
for(int i=0;i<array.length;i++){
System.out.print(array[i]+" ");
}
//希尔排序
int gap = array.length;
while (true) {
gap /= 2; //增量每次减半
for (int i = 0; i < gap; i++) {
for (int j = i + gap; j < array.length; j += gap) {//这个循环里其实就是一个插入排序
int temp = array[j];
int k = j - gap;
while (k >= 0 && array[k] > temp) {
array[k + gap] = array[k];
k -= gap;
}
array[k + gap] = temp;
}
}
if (gap == 1)
break;
}
System.out.println();
System.out.println("排序之后:");
for(int i=0;i<array.length;i++){
System.out.print(array[i]+" ");
}
}
}
希尔排序是不稳定的,但是时间复杂度肯定小于O( n 2 n^2 n2)。
6. 选择排序
从1开始,1与2比较,记录最小值为k。
k与3比较,把最小值给k。再与4比较,直到与n比较。一轮结束。
从2开始,继续循环,直到循环到n-1开始,则结束。
bilibili视频演示动画–>数据结构排序算法之选择排序演示
代码演示:
public class Test {
public static void main(String[] args){
int[] array={49,38,65,97,76,13,27,49,78,34,12,64,1};
System.out.println("排序之前:");
for(int i=0;i<array.length;i++){
System.out.print(array[i]+" ");
}
System.out.println();
selectionSort(array);
System.out.println("排序之后:");
for(int i=0;i<array.length;i++){
System.out.print(array[i]+" ");
}
}
public static void selectionSort(int[] arr){
for (int i = 0; i < arr.length - 1; i++) {
int min = i;
for (int j = i + 1; j < arr.length; j++) {
if (arr[min] > arr[j]) {
min = j;
}
}
if (min != i) {
int tmp = arr[min];
arr[min] = arr[i];
arr[i] = tmp;
}
}
}
}
选择排序时间复杂度也为 O( n 2 n^2 n2)。
7. 堆排序
首先将一组数建成堆(二叉树)。
将其变为大顶堆。(从大到小为小顶堆)
(大顶堆:父节点必大于子节点,若小于,则交换、小顶堆反之亦然)
然后交换跟节点和最小叶子节点。去除叶子节点(同一个节点,先去除右节点)。
循环即可。
bilibili视频演示动画–>数据结构排序算法之堆排序演示
代码实现:
public class Test {
public static void main(String[] args){
int[] array={49,38,65,97,76,13,27,49,78,34,12,64,1};
System.out.println("排序之前:");
for(int i=0;i<array.length;i++){
System.out.print(array[i]+" ");
}
System.out.println();
heapSort(array);
System.out.println("排序之后:");
for(int i=0;i<array.length;i++){
System.out.print(array[i]+" ");
}
}
public static int[] heapSort(int[] array) {
//这里元素的索引是从0开始的,所以最后一个非叶子结点array.length/2 - 1
for (int i = array.length / 2 - 1; i >= 0; i--) {
adjustHeap(array, i, array.length); //调整堆
}
// 上述逻辑,建堆结束
// 下面,开始排序逻辑
for (int j = array.length - 1; j > 0; j--) {
// 元素交换,作用是去掉大顶堆
// 把大顶堆的根元素,放到数组的最后;换句话说,就是每一次的堆调整之后,都会有一个元素到达自己的最终位置
swap(array, 0, j);
// 元素交换之后,毫无疑问,最后一个元素无需再考虑排序问题了。
// 接下来我们需要排序的,就是已经去掉了部分元素的堆了,这也是为什么此方法放在循环里的原因
// 而这里,实质上是自上而下,自左向右进行调整的
adjustHeap(array, 0, j);
}
return array;
}
/**
* 整个堆排序最关键的地方
* @param array 待组堆
* @param i 起始结点
* @param length 堆的长度
*/
public static void adjustHeap(int[] array, int i, int length) {
// 先把当前元素取出来,因为当前元素可能要一直移动
int temp = array[i];
for (int k = 2 * i + 1; k < length; k = 2 * k + 1) { //2*i+1为左子树i的左子树(因为i是从0开始的),2*k+1为k的左子树
// 让k先指向子节点中最大的节点
if (k + 1 < length && array[k] < array[k + 1]) { //如果有右子树,并且右子树大于左子树
k++;
}
//如果发现结点(左右子结点)大于根结点,则进行值的交换
if (array[k] > temp) {
swap(array, i, k);
// 如果子节点更换了,那么,以子节点为根的子树会受到影响,所以,循环对子节点所在的树继续进行判断
i = k;
} else { //不用交换,直接终止循环
break;
}
}
}
/**
* 交换元素
* @param arr
* @param a 元素的下标
* @param b 元素的下标
*/
public static void swap(int[] arr, int a, int b) {
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
}
8. 基数排序
LSD:最低位优先(Least Significant Digit first)法。先从个位开始。
MSD:最高位优先(Most Significant Digit first)法。先从最高位开始。
假设原来有一串数值如下所示:
73, 22, 93, 43, 55, 14, 28, 65, 39, 81
首先根据个位数的数值,在走访数值时将它们分配至编号0到9的桶子中:
0
1 81
2 22
3 73 93 43
4 14
5 55 65
6
7
8 28
9 39
第二步
接下来将这些桶子中的数值重新串接起来,成为以下的数列:
81, 22, 73, 93, 43, 14, 55, 65, 28, 39
接着再进行一次分配,这次是根据十位数来分配:
0
1 14
2 22 28
3 39
4 43
5 55
6 65
7 73
8 81
9 93
第三步
接下来将这些桶子中的数值重新串接起来,成为以下的数列:
14, 22, 28, 39, 43, 55, 65, 73, 81, 93
这时候整个数列已经排序完毕;如果排序的对象有三位数以上,则持续进行以上的动作直至最高位数为止。
代码实现:
public class Test {
public static void main(String[] args){
int[] array={49,38,65,97,76,13,27,49,78,34,12,64,1};
System.out.println("排序之前:");
for(int i=0;i<array.length;i++){
System.out.print(array[i]+" ");
}
System.out.println();
radixsort(array,2);
System.out.println("排序之后:");
for(int i=0;i<array.length;i++){
System.out.print(array[i]+" ");
}
}
public static void radixsort(int[] number, int d) //d表示最大的数有多少位
{
int k = 0;
int n = 1;
int m = 1; //控制键值排序依据在哪一位
int[][]temp = new int[10][number.length]; //数组的第一维表示可能的余数0-9
int[]order = new int[10]; //数组orderp[i]用来表示该位是i的数的个数
while(m <= d)
{
for(int i = 0; i < number.length; i++)
{
int lsd = ((number[i] / n) % 10);
temp[lsd][order[lsd]] = number[i];
order[lsd]++;
}
for(int i = 0; i < 10; i++)
{
if(order[i] != 0)
for(int j = 0; j < order[i]; j++)
{
number[k] = temp[i][j];
k++;
}
order[i] = 0;
}
n *= 10;
k = 0;
m++;
}
}
}
9. 归并排序
挺复杂的。
代码实现:
public class Test {
public static void main(String[] args){
int[] array={49,38,65,97,76,13,27,49,78,34,12,64,1};
System.out.println("排序之前:");
for(int i=0;i<array.length;i++){
System.out.print(array[i]+" ");
}
System.out.println();
mergeSort(array);
System.out.println("排序之后:");
for(int i=0;i<array.length;i++){
System.out.print(array[i]+" ");
}
}
public static void mergeSort(int[] arr) {
sort(arr, 0, arr.length - 1);
}
public static void sort(int[] arr, int L, int R) {
if(L == R) {
return;
}
int mid = L + ((R - L) >> 1);
sort(arr, L, mid);
sort(arr, mid + 1, R);
merge(arr, L, mid, R);
}
public static void merge(int[] arr, int L, int mid, int R) {
int[] temp = new int[R - L + 1];
int i = 0;
int p1 = L;
int p2 = mid + 1;
// 比较左右两部分的元素,哪个小,把那个元素填入temp中
while(p1 <= mid && p2 <= R) {
temp[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
}
// 上面的循环退出后,把剩余的元素依次填入到temp中
// 以下两个while只有一个会执行
while(p1 <= mid) {
temp[i++] = arr[p1++];
}
while(p2 <= R) {
temp[i++] = arr[p2++];
}
// 把最终的排序的结果复制给原数组
for(i = 0; i < temp.length; i++) {
arr[L + i] = temp[i];
}
}
}