你都是会点啥技术(六)--- 数据结构

你都是会点啥技术(六)— 数据结构

写在前面的话:数据结构基本知识和排序。

链接:https://pan.baidu.com/s/1BJnOjGK3k0DKq9q81xKC2w
提取码:kkjy
在这里插入图片描述

程序=数据结构+算法
(数据结构指的是数据与数据之间的逻辑关系 算法指的是解决特定问题的步骤和方法)

1.1 什么是数据结构

利用计算机解决问题的过程: 将具体问题抽象成一个数学模型,设计出解决此数学模型的算法,进行编程、测试、调整、解答。

线性关系: 两个变量之间存在一次方函数关系,就称它们之间存在线性关系。(y=kx+b (k,b为常数) x与y是线性关系)。

数据结构是一门研究非数值计算的程序设计问题中计算机的操作对象以及它们之间的关系和操作等的学科。

数据结构所处的位置:
在这里插入图片描述

1.2基本概念和术语

数据: 对客观事物的符号表示,在计算机科学中是指所有能输入到计算机中并被计算机程序处理的符号的总称。(图形,声音等)
数据元素: 数据的基本单位,在计算机程序中通常作为一个整体进行考虑和处理,一个数据元素由多个数据项组成。(用户表中的一条用户信息)
数据项: 数据不可分割的最小单位。(用户信息中的用户名、年龄等)
数据对象: 性质相同的数据元素的集合,是数据的一个子集。
数据结构: 相互之间存在一种或多种特定关系的数据元素的集合。Data_Structure = (D,S) D是数据元素的有限集,S是D上关系的有限集。
在这里插入图片描述
数据的逻辑结构:
在这里插入图片描述
在这里插入图片描述
集合结构: 结构中的数据元素之间除了“同属于一个集合”的关系外,别无其他关系。
线性结构: 结构中的数据元素之间存在一对一的关系。
树形结构: 结构中的数据元素之间存在一对多的关系。
图形结构: 结构中的数据之间存在多对多的关系。
数据的存储结构:
在这里插入图片描述
在这里插入图片描述
数据类型: 一个计算机硬件系统通常含有“位”、“字节”、“字”等原子类型,它们的操作通过计算机设计的一套指令系统直接由电路系统完成。
高级程序语言提供的数据类型,其操作需要通过编译器或解释器转化成底层,即汇编语言或机器语言的数据类型来实现。
(引入数据类型的目的,从硬件角度看,是作为解释计算机内存中信息含义的一种手段,对于使用数据类型的用户来说,例如用户不需要知道"整数"
在计算机内部是如何表示的,也不需要知道其操作时如何实现的。如“两整数求和”,程序设计者仅注重“数学上求和”抽象特征,而不是其硬件的“位”操作如何进行)

机器语言:是纯粹的二进制数据表示的语言,是电脑可以真正识别的语言。汇编语言和高级语言经过编译连接最终都会变成机器语言才能被CPU识别和运行。
汇编语言:是将二进制的机器码通过助记符的方式让人可以更方便的编写并检查的低级语言,是最接近CPU运行原理的较为通俗的比较容易理解的语言。
高级语言:是以“人”的思维逻辑来描述电脑运行的语言,完全脱离了CPU的“思维”模式,所以可移植性很高。

抽象数据类型: 指一个数学模型以及定义在该模型上的一组操作。抽象数据类型的定义由一个值域和定义在该值域上的一组操作组成,按其值的不同特性,可
分为3中类型:原子类型(值不可分解),固定聚合类型(值由确定数目的成分按某种结构组成),可变聚合类型(值由不确定数目的成分按 某种结构组成)。(D,S,P)D是数据对象,S是D上的关系集,P是对D的基本操作集。
抽象数据类型格式表示:
ADT抽象数据类型名{
数据对象:数据对象的定义
数据关系:数据关系的定义
基本操作:基本操作的定义
}ADT抽象数据类型名

基本操作名(参数表)
初始条件:(初始条件描述)
操作结果:(操作结果描述)
多形数据类型: 指其值不确定的数据类型,从抽象数据类型来看具有相同的数学抽象特性。

1.3算法和算法分析

算法: 是对特定问题求解步骤的一种描述,它是指令的有限序列,其中每一条指令表示一个或多个操作。

算法特性

有穷性: 一个算法必须总是对任何合法的输入值在执行有穷步之后结束,且每一步都可在有穷时间内完成。
确定性: 算法中每一条指令必须有确切的含义,读者理解时不会产生二义性。并且,在任何条件下,算法只有唯一的一条执行路径,即对于相同的输入只能得 出相同写输出
可行性: 一个算法是能行的,即算法中描述的操作都是可以通过已经实现的基本运算执行有限次来实现的。
输入: 一个算法有零个或多个输入,这些输入取自于某个特定的对象的集合。
输出: 一个算法有一个或多个的输出,这些输出是同输入有着某些特定关系的量。

算法设计要求
正确性: 算法应当满足具体问题的需求。
可读性: 算法主要为了人的阅读与交流,其次才是机器执行。
健壮性: 当输入数据非法时,算法也能适当地做出反应或进行处理,而不会产生莫名其妙的输出结果。
效率与低存储量需求: 效率指算法执行的时间,对于同一个问题如果有多个算法可以解决,执行时间短得算法效率高。
存储量需求指算法执行过程中所需要的最大存储空间。
算法的时间量度记作: T(n) = O(f(n)) 它表示随问题规模n的增大,算法执行时间的增长率和f(n)的增长率相同,称做算法的渐进时间复杂度,简称时间复杂度。
T(n)一个算法中的语句执行次数称为语句频度,记为T(n)。随着问题规模n的不断增大,时间复杂度不断增大,算法的执行效率越低。
常数项可以忽略|低次项可以忽略|系数可以忽略
常见的时间复杂度(从小到大):
常数阶O(1)、对数阶O(log2n)、线性阶O(n)、线性对数阶O(nlog2n)、平方阶O(n2)、立方阶O(n3)、k次方阶O(n^k)、指数阶O(2n)
计算时间复杂度的方法:
用常数1代替运行时间中的所有加法常数;修改后的运行次数函数中,只保留最高阶项;去除最高阶项的系数。
如T(n)=n2+5n+6和T(n)=3n2+3n+2他们的T(n)不同,但计算后的时间复杂度相同,都为O(n^2)
常见函数的增长率:
在这里插入图片描述
算法所需存储空间的量度: S(n)=O(f(n)) n为问题的规模

2.1线性结构

线性结构的特点是: 在数据元素的非空有限集中
存在惟一的一个被称做“第一个”的数据元素
存在惟一的一个被称做“最后一个”的数据元素
除第一个之外,集合中的每个数据元素均只有一个前驱
除最后一个之外,集合中每个数据元素均只有一个后继

线性表: 一个线性表是n个数据元素的有限序列。
在这里插入图片描述
线性链表: 用一组任意的存储单位存储线性表的数据元素(数据域和和指针域)
在这里插入图片描述
在这里插入图片描述
静态链表: 用数组的一个分量表示一个结点,同时用游标(指示器cur)代替指针指示结点在数组中的相对位置。数组的第零分量可
看成头结点,其指针区域指示链表的第一个结点。
在这里插入图片描述
循环链表: 是另一种形式的链式存储结构,它的特点是表中最后一个结点的指针域指向头结点,整个链表形成一个环。
在这里插入图片描述
**双向链表:**结点有两个指针域,其一指向直接后继,另一指向直接前驱
在这里插入图片描述

2.2栈:先进后出,后进先出的线性表

在这里插入图片描述

2.3队列:先进先出的线性表

在这里插入图片描述

2.4串

1.串的定义: 串是由零个或多个组成的有序队列。
2.串的长度: 串中字符的数目称为串的长度。
3.空串: 由零个字符组成的串叫做空串,空串不包括任何字符,其长度为零。
4.子串: 串中任意个连续的字符组成的子序列称为该串的子串,空串是任何串的子串。
5.主串: 包含子串的串相应的称为主串。
串的表示:
串有两种表示形式: 顺序存储表示和链式存储表示。

串的存储结构最好使用顺序存储,而且使用数组是较好,较明了的方法。
对于使用链式存储,若是一个结点存储一个字符,会占用大量的空间去存放指针域。
若是使用一个结点存储多个字符,如同上面图像所描绘,虽然占的空间消耗小了,但是相对于数组,依旧需要太多的额外空间,最不好的是,这种方法会时链表的快速插入删除操作的优点消失,甚至复杂了。
所以使用顺序存储的方法是更加好的。

2.5树

简单定义:树是由n(n>0)个有限节点组成一个具有层次关系的集合。

术语:
节点的度: 一个节点含有的子树的个数称为该节点的度;
树的度: 一棵树中,最大的节点度称为树的度;
叶节点或终端节点: 度为零的节点;
非终端节点或分支节点: 度不为零的节点;
父亲节点或父节点: 若一个节点含有子节点,则这个节点称为其子节点的父节点;
孩子节点或子节点: 一个节点含有的子树的根节点称为该节点的子节点;
兄弟节点: 具有相同父节点的节点互称为兄弟节点;
节点的层次: 从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
深度: 对于任意节点n,n的深度为从根到n的唯一路径长,根的深度为0;
高度: 对于任意节点n,n的高度为从n到一片树叶的最长路径长,所有树叶的高度为0;
堂兄弟节点 : 父节点在同一层的节点互为堂兄弟;
节点的祖先: 从根到该节点所经分支上的所有节点;
子孙: 以某节点为根的子树中任一节点都称为该节点的子孙。
森林: 由m(m>=0)棵互不相交的树的集合称为森林;
树种类:
无序树: 树中任意节点的子节点之间没有顺序关系,这种树称为无序树,也称为自由树;
有序树: 树中任意节点的子节点之间有顺序关系,这种树称为有序树;
二叉树: 每个节点最多含有两个子树的树称为二叉树;
完全二叉树: 对于一颗二叉树,假设其深度为d(d>1)。除了第d层外,其它各层的节点数目均已达最大值,且第d层所有节点从左向右连续地紧密排列,这样的二叉树被称为完全二叉树;
满二叉树: 所有叶节点都在最底层的完全二叉树;
平衡二叉树(AVL树): 当且仅当任何节点的两棵子树的高度差不大于1的二叉树;
排序二叉树(二叉查找树(英语:Binary Search Tree)): 也称二叉搜索树、有序二叉树;
霍夫曼树: 带权路径最短的二叉树称为哈夫曼树或最优二叉树;
B树: 一种对读写操作进行优化的自平衡的二叉查找树,能够保持数据有序,拥有多于两个子树。
红黑树: 红黑树是一颗特殊的二叉查找树,除了二叉查找树的要求外,它还具有以下特性:
每个节点或者是黑色,或者是红色。
根节点是黑色。
每个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!]
如果一个节点是红色的,则它的子节点必须是黑色的。
从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。
在这里插入图片描述
B-tree(B-树或者B树)
一颗m阶B树的特性:

根结点至少有两个子女(如果B树只有一个根节点,这个根节点的key的数量可以为[1~m-1])
每个非根节点所包含的关键字个数 j 满足:⌈m/2⌉ - 1 <= j <= m - 1,节点的值按非降序方式存放,即从左到右依次增加
除根结点以及叶子节点以外的所有结点的度数正好是关键字总数加1,故内部节点的子树个数 k 满足:⌈m/2⌉ <= k <= m
所有的叶子结点都位于同一层
假定:
m:B树的阶
n:非根的内部节点键的个数
t:m阶B树的节点能存在的最小的度
则有:
⌈m/2⌉ - 1 <= n <= m - 1
t - 1 <= n <= 2t -1

B+树
m阶B+树是m阶B-tree的变体,它的定义大致跟B-tree一致,不过有以下几点不同:

有n棵子树的结点中含有n个关键字,每个关键字不保存数据,只用来索引,所有数据都保存在叶子节点,其中⌈m/2⌉ <= n <= m
所有的叶子结点中包含了全部关键字的信息,及指向含这些关键字记录的指针,且叶子结点本身依关键字的大小自小而大顺序链接
所有的非终端结点可以看成是索引部分,结点中仅含其子树(根结点)中的最大(或最小)关键字
通常在B+树上有两个头指针,一个指向根结点,一个指向关键字最小的叶子结点

B*树
B*树是B+树的变体,除了B+树的要求之外,还有以下特性:

⌈m*2/3⌉ <= n <=m 这里的n是除根节点之外的内部节点的键
增加内部节点中兄弟节点的指针,由左边指向右边
在这里插入图片描述

来吧,排序!

三大查找方法
顺序查找,二分法查找(折半查找),分块查找

顺序查找的基本思想:
从表的一端开始,顺序扫描表,依次将扫描到的结点关键字和给定值(假定为a)相比较,若当前节点关键字与a相等,则查找成功;若扫描结束后,扔未找到关键字等于a的结点,则查找失败。
说白了就是,从头到尾,一个一个地比,找着相同的就成功,找不到就失败。很明显的缺点就是查找效率低。
适用于线性表的顺序存储结构和链式存储结构。
在这里插入图片描述

计算平均查找长度。
例如上表,查找1,需要1次,查找2需要2次,依次往下推,可知查找16需要16次,
可以看出,我们只要将这些查找次数求和(我们初中学的,上底加下底乘以高除以2),然后除以结点数,即为平均查找长度。
设n=节点数
平均查找长度 =(n+1)/2

二分法查找(折半查找)的基本思想:
前提:
(1)确定该区间的中点位置:mid = (low+high)/2
mid代表区间中间的结点的位置,low代表区间最左结点位置,height代表区间最右结点位置。
(2)将待查a值与结点mid的关键字(下面用R[mid].key)比较,若相等,则查找成功,否则确定新的查找区间:
如果R[mid].key>a,则由表的有序性可知,R[mid].key右侧的值大于a,所以等于a的关键字如果存在,必然在R[mid].key左边的表中。这时height=mid-1。
如果R[mid].key<a,则等于a的关键字如果存在,必然在R[mid].key右边的表中。这时low=mid。
如果R[mid].key=a,则查找成功。
(3)下一次查找针对新的查找区间,重复步骤(1)和(2)
(4)在查找过程中,low逐步增加,hight逐步减少,如果high<low,则查找失败。
在这里插入图片描述


/**
 * 
 * @description: 数组二分法查找
 * @author: libl
 * @date: 2019年3月12日
 */
public class BinarySearch {
	public static void main(String[] args) {
		int[] arr = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
		int target = 5;
		int begin = 0;
		int end = arr.length;
		int mid = (begin + end) / 2;
		int index = -1;
		while (true) {
			if(begin>=end) {
				break;
			}
			if (arr[mid] == target) {
				index = mid;
				break;
			} else {
				if (arr[mid] > target) {
					end = mid - 1;
				} else {
					begin = mid + 1;
				}
			}
			mid = (begin + end) / 2;
		}
		System.out.println(index);
	}
}


平均查找长度=Log2(n+1)-1
注:虽然二分法查找的效率高,但是要将表按关键字排序。而排序本身是一种很费时的运算,所以二分法比较适用于顺序存储结构。为保持表的有序性,在顺序结构中插入和删除都必须移动大量的结点。因此,二分查找特别适用于那种一经建立就很少改动而又经常需要查找的线性表。

分块查找的基本思想:
二分查找表使分块有序的线性表和索引表(抽取各块中的最大关键字及其起始位置构成索引表)组成,由于表是分块有序的,所以索引表是一个递增有序表,因此采用顺序或二分查找索引表,以确定待查结点在哪一块,由于块内无序,只能用顺序查找。在这里插入图片描述

设表共n个结点,分b块,s=n/b
(分块查找索引表)平均查找长度=Log2(n/s+1)+s/2
(顺序查找索引表)平均查找长度=(S2+2S+n)/(2S)

注:分块查找的优点是在表中插入或删除一个记录时,只要找到该记录所属块,就在该块中进行插入或删除运算(因块内无序,所以不需要大量移动记录)。它主要代价是增加一个辅助数组的存储控件和将初始表分块排序的运算。

它的性能介于顺序查找和二分查找之间。

八大排序

数据结构常见的八大排序算法:
在这里插入图片描述
性能比较:
在这里插入图片描述

1.直接插入排序

在这里插入图片描述
直接插入排序的核心思想就是:将数组中的所有元素依次跟前面已经排好的元素相比较,如果选择的元素比已排序的元素小,则交换,直到全部元素都比较过。因此,从上面的描述中我们可以发现,直接插入排序可以用两个循环完成:
1.第一层循环:遍历待比较的所有数组元素
2.第二层循环:将本轮选择的元素(selected)与已经排好序的元素(ordered)相比较。
如果:selected > ordered,那么将二者交换。


import java.util.Arrays;

/**
 * 
 * @description:插入排序-直接插入排序
 * @author: libl
 * @date: 2019年3月25日
 */
public class InsertSort {
	public static void main(String[] args) {
		int arr[] = new int[] { 12, 32, 22, 7, 48 };
		insertSort(arr);
		System.out.println(Arrays.toString(arr));
	}

	public static void insertSort(int arr[]) {
		// 遍历所有的元素
		for (int i = 1; i < arr.length; i++) {
			// 如果当前元素比前一个元素小
			if (arr[i] < arr[i - 1]) {
				// 把当前遍历元素存起来
				int temp = arr[i];
				int j;
				for (j = i - 1; j >= 0 && temp < arr[j]; j--) {
					// 把前一个元素赋给后一个元素
					arr[j + 1] = arr[j];
				}
				// 把临时变量(外层for循环的当前元素)赋给不满足条件的后一个元素
				arr[j + 1] = temp;
			}
		}
	}
}

2.希尔排序

在这里插入图片描述
希尔排序的算法思想:将待排序数组按照步长gap进行分组,然后将每组的元素利用直接插入排序的方法进行排序;每次将gap折半减少,循环上述操作;当gap=1时,利用直接插入,完成排序。
同样的:从上面的描述中我们可以发现:希尔排序的总体实现应该由三个循环完成:
1.第一层循环:将gap依次折半,对序列进行分组,直到gap=1。
2.第二、三层循环:和直接插入排序所需要的两次循环,具体描述见直接插入排序。

import java.util.Arrays;

/**
 * 
 * @description:插入排序-希尔排序
 * @author: libl
 * @date: 2019年5月10日
 */
public class ShellSort {
	public static void main(String[] args) {
		int[] arr = new int[] { 9, 1, 2, 5, 7, 4, 8, 6, 3, 5 };
		shellSort(arr);
		System.out.println(Arrays.toString(arr));
	}

	public static void shellSort(int[] arr) {
		// 遍历所有的步长
		for (int d = arr.length / 2; d > 0; d /= 2) {
			// 遍历所有的元素
			for (int i = d; i < arr.length; i++) {
				// 遍历本组中所有的元素
				for (int j = i - d; j >= 0; j -= d) {
					// 如果当前元素大于加上步长后的那个元素
					if (arr[j] > arr[j + d]) {
						int temp = arr[j];
						arr[j] = arr[j + d];
						arr[j + d] = temp;
					}
				}
			}
		}
	}
}

3.简单选择排序

在这里插入图片描述
简单选择排序的基本思想:比较+交换
1.从待排序序列中,找到关键字最小的元素
2.如果最小元素不是待排序序列的第一个元素,将其和第一个元素互换
3.从余下的N-1个元素中,找出关键字最小的元素,重复(1)、(2)步,直到排序结束。
因此我们可以发现:简单选择排序也是通过两层循环实现。
第一层循环:依次遍历序列当中的每一个元素
第二层循环:将遍历得到的当前元素依次与余下的元素进行比较,符合最小元素的条件,则交换。

import java.util.Arrays;

/**
 * 
 * @description:简单选择排序
 * @author: libl
 * @date: 2019年5月10日
 */
public class SelectSort {
	public static void main(String[] args) {
		int[] arr = { 12, 32, 22, 7, 48 };
		selectSort(arr);
		System.out.println(Arrays.toString(arr));
	}

	// 选择排序
	public static void selectSort(int[] arr) {
		// 遍历所有的数
		for (int i = 0; i < arr.length; i++) {
			int minIndex = i;
			// 把当前遍历的数和后面所有的数依次进行比较,并记录下最小的数的下标
			for (int j = i + 1; j < arr.length; j++) {
				// 如果后面比较的数比记录的最小的数小
				if (arr[minIndex] > arr[j]) {
					// 记录下最小的那个数的下标
					minIndex = j;
				}
			}
			// 如果最小的数和当前遍历数的下标不一致,说明下标为minIndex的数比当前遍历的数更小
			if (i != minIndex) {
				int temp = arr[i];
				arr[i] = arr[minIndex];
				arr[minIndex] = temp;
			}
		}
	}

}

4.堆排序

堆的概念
堆:本质是一种数组对象。特别重要的一点性质:任意的叶子节点小于(或大于)他所有的父节点。对此,又分为大顶堆和小顶堆,大顶堆要求节点的元素都要大于其孩子,小顶堆要求节点元素都小于其左右孩子,两者对左右孩子的大小关系不做任何要求。
利用堆排序,就是基于大顶堆或者小顶堆的一种排序方法。下面,我们通过大顶堆来实现。
基本思想:
堆排序可以按照以下步骤来完成:
1.首先将序列构建称为大顶堆;
(这样满足了大顶堆那条性质:位于跟节点的元素一点是当前序列的最大值)
在这里插入图片描述
2.取出当前大顶堆的根节点,将其与序列末尾元素进行交换
(此时:序列末尾的元素为已排序的最大值,由于交换了元素,当前位于根节点的堆并不一定满足大顶堆的性质)
3.对交换后的n-1个序列元素进行调整,使其满足大顶堆的性质
在这里插入图片描述
4.重复2.3步骤,直至堆中只有1个元素为止

import java.util.Arrays;

public class HeapSort {

	public static void main(String[] args) {
		int[] b = { 16, 14, 10, 8, 7, 9, 3, 2, 4, 1 };
		heapSort(b, b.length - 1);
		System.out.print(Arrays.toString(b));
	}

	public static void heapSort(int[] a, int len) {
		int i;
		for (i = len / 2; i >= 0; i--) { /* 把a[]构造成一个大顶堆 */
			HeapAdjust(a, i, len);
		}
		for (i = len; i > 0; i--) {
			swap(a, 0, i); /* 交换堆顶最大元素和堆尾最小元素 */
			HeapAdjust(a, 0, i - 1); /* 把交换后的堆a[0,i-1],再次构造成大顶顶,使堆顶元素为最大值 */
		}
	}

	static void HeapAdjust(int[] a, int start, int len) {
		int temp, j;
		temp = a[start];
		for (j = 2 * start; j <= len; j *= 2) { /* 从index最大的有孩子的节点开始筛选,堆排 */
			if (j < len && a[j] < a[j + 1]) /* 是index=j的元素为较大的元素 */
				j++;
			if (temp >= a[j])
				break;
			a[start] = a[j]; /* 将较大元素赋值给父节点 */
			start = j;
		}
		a[start] = temp;
	}

	static void swap(int a[], int low, int high) {
		int temp = a[low];
		a[low] = a[high];
		a[high] = temp;
	}
}
5.冒泡排序

在这里插入图片描述
冒泡排序思路比较简单:
1.将序列当中的左右元素,依次比较,保证右边的元素始终大于左边的元素(第一轮结束后,序列最后一个元素一定是当前序列的最大值)
2.对序列当中剩下的n-1个元素再次执行步骤1
3.对于长度为n的序列,一个需要执行n-1轮比较(利用while循环可以减少执行次数)

import java.util.Arrays;

/**
 * 
 * @description:选择排序-冒泡排序
 * @author: libl
 * @date: 2019年3月24日
 */
public class BubbleSort {
	public static void main(String[] args) {
		int arr[] = new int[] { 3, 44, 38, 5, 15 };
		bubbleSort(arr);
		System.out.println(Arrays.toString(arr));
	}

	public static void bubbleSort(int arr[]) {
		// 控制共比较多少轮 arr.length-1
		for (int i = 0; i < arr.length - 1; i++) {
			// 控制比较的次数
			for (int j = 0; j < arr.length - 1 - i; j++) {
				if (arr[j] > arr[j + 1]) {
					int temp = arr[j + 1];
					arr[j + 1] = arr[j];
					arr[j] = temp;
				}
			}
		}
	}

}

6.快速排序

在这里插入图片描述
快速排序的基本思想:挖坑填数+分治法
1.从序列当中选择一个基准数(pivot)
在这里我们选择序列当中第一个数为基准数
2.将序列当中的所有数依次遍历,比基准数大的位于其右侧,比基准数小的位于其左侧
3.重复步骤1,2,直到所有子集当中只有一个元素为止
用伪代码描述如下:
1.i =L; j = R; 将基准数挖出形成第一个坑a[i]。
2.j–由后向前找比它小的数,找到后挖出此数填前一个坑a[i]中。
3.i++由前向后找比它大的数,找到后也挖出此数填到前一个坑a[j]中。
4.再重复执行2,3二步,直到i==j,将基准数填入a[i]中

import java.util.Arrays;

/**
 * 
 * @description:选择排序-快速排序
 * @author: libl
 * @date: 2019年3月25日
 */
public class QuickSort {
	public static void main(String[] args) {
		int arr[] = new int[] { 0, 5, 2, 4, 3, 7, 5, 8, 8, 9, 1 };
		quickSort(arr, 0, arr.length - 1);
		System.out.println(Arrays.toString(arr));
	}

	public static void quickSort(int arr[], int start, int end) {
		if (start < end) {
			// 把数组中的第0个数字做为标准数
			int stard = arr[start];
			// 记录需要排序的下标
			int low = start;
			int high = end;
			// 循环找比标准数大的数和比标准数小的数
			while (low < high) {
				// 右边的数字比标准数大
				while (low < high && stard <= arr[high]) {
					high--;
				}
				// 否则,使用右边的数字替换左边的数
				arr[low] = arr[high];
				// 左边的数字比标准数小
				while (low < high && arr[low] <= stard) {
					low++;
				}
				// 否则,把左边的数替换到右边
				arr[high] = arr[low];
			}
			// 把标准数赋值给低或高所在的位置
			arr[low] = stard;
			// 处理左边小数
			quickSort(arr, 0, low);
			// 处理右边大数
			quickSort(arr, low + 1, end);
		}
	}
}

7.归并排序

在这里插入图片描述
归并排序:
1.归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法的一个典型的应用。它的基本操作是:将已有的子序列合并,达到完全有序的序列;即先使每个子序列有序,再使子序列段间有序
2.归并排序其实要做两件事
分解----将序列每次拆办拆分
合并----将划分后的序列段两两排序合并
因此,归并排序实际上就是两个操作,拆分+合并
如何合并?
L[first…mid]为第一段,L[mid+1…last]为第二段,并且两端已经有序,现在我们要将两端合成L[first…last]并且也有序。
(1)首先依次从第一段与第二段中取出元素比较,将较小的元素赋值给temp[]
(2)重复执行上一步,当某一段赋值结束,则将另一段剩下的元素赋值给temp[]
(3)此时将temp[]中的元素复制给L[],则得到的L[first…last]有序
如何分解
在这里,我们采用递归的方法,首先将待排序列分为A,B两组;然后重复对A、B序列分组;直到分组后组内只有一个元素,此时我们认为组内所有元素有序,则分组结束。

import java.util.Arrays;

/**
 * 
 * @description:归并排序
 * @author: libl
 * @date: 2019年7月11日
 */
public class MegerSorrt {
	public static void main(String[] args) {
		int arr[] = new int[] { 15, 36, 33, 4, 22, 48 };
		mergeSort(arr, 0, arr.length - 1);
		System.out.println(Arrays.toString(arr));
	}

	public static void mergeSort(int arr[], int low, int high) {
		int middle = (high + low) / 2;
		if (low < high) {
			// 处理左边
			mergeSort(arr, low, middle);
			// 处理右边
			mergeSort(arr, middle + 1, high);
			// 归并
			merge(arr, low, middle, high);
		}
	}

	public static void merge(int[] arr, int low, int middle, int high) {
		// 用于存储归并后的临时数组
		int[] temp = new int[high - low + 1];
		// 记录第一个数组中需要遍历的下标
		int i = low;
		// 记录第二个数组中需要遍历的下标
		int j = middle + 1;
		// 用于记录在临时数组中存放的下标
		int index = 0;
		// 遍历两个数组取出小的数字,放入临时数组中
		while (i <= middle && j <= high) {
			// 第一个数组的数据更小
			if (arr[i] <= arr[j]) {
				// 把小的数据放入临时数组中
				temp[index] = arr[i];
				// 让下标向后移一位
				i++;
			} else {
				temp[index] = arr[j];
				j++;
			}
			index++;
		}
		// 处理多余的数据
		while (j <= high) {
			temp[index] = arr[j];
			j++;
			index++;
		}
		while (i <= middle) {
			temp[index] = arr[i];
			i++;
			index++;
		}
		// 把临时数组中的数据重新存入原数组
		for (int k = 0; k < temp.length; k++) {
			arr[k + low] = temp[k];
		}
	}
}

8.基数排序

在这里插入图片描述
基数排序:
1.基数排序:通过序列中各个元素的值,对排序的N个元素进行若干趟的“分配”与“收集”来实现排序。
分配:我们将L[i]中的元素取出,首先确定某个位上的数字,根据该数字分配到与之序号相同的桶中
收集:当序列中所有的元素都分配到对应的桶中,再按照顺序依次将桶中的元素收集形成新的一个待排序列L[]
对新形成的序列L[]重复执行分配和收集元素中的十位、百位…直到分配完该序列中的最高位,则排序结束。

import java.util.Arrays;

/**
 * 
 * @description:基数排序
 * @author: libl
 * @date: 2019年7月11日
 */
public class RadixSort {
	public static void main(String[] args) {
		int arr[] = new int[] { 1, 82, 127, 599, 622, 743 };
		radixSort(arr);
		System.out.println(Arrays.toString(arr));
	}

	private static void radixSort(int[] arr) {
		// 存数组中最大的数字
		int max = Integer.MIN_VALUE;
		for (int i = 0; i < arr.length; i++) {
			if (arr[i] > max) {
				max = arr[i];
			}
		}
		// 计算最大数字是几位数
		int maxLength = (max + "").length();
		// 用于临时存储数据的队列的数组
		QueueUtils[] temp = new QueueUtils[10];
		// 为队列数组赋值
		for (int i = 0; i < temp.length; i++) {
			temp[i] = new QueueUtils();
		}
		// 根据最大长度的数决定比较的次数
		for (int i = 0, n = 1; i < maxLength; i++, n *= 10) {
			// 把每一个数字分别计算余数
			for (int j = 0; j < arr.length; j++) {
				// 计算余数
				int ys = arr[j] / n % 10;
				// 把当前遍历的数据放入指定的队列中
				temp[ys].add(arr[j]);
			}
			// 记录取的元素需要放的位置
			int index = 0;
			// 把所有队列中的数字取出来
			for (int k = 0; k < temp.length; k++) {
				// 循环取出元素
				while (!temp[k].isEmpty()) {
					// 取出元素
					arr[index] = temp[k].poll();
					// 记录下一个位置
					index++;
				}
			}

		}
	}

}

/**
 * 
 * @description:实现一个队列
 * @author: libl
 * @date: 2019年7月11日
 */
class QueueUtils {
	private int[] elements;

	public QueueUtils() {
		elements = new int[0];
	}

	public void add(int element) {
		int[] newArr = new int[elements.length + 1];
		newArr[elements.length] = element;
		for (int i = 0; i < elements.length; i++) {
			newArr[i] = elements[i];
		}
		elements = newArr;
	}

	public int poll() {
		int[] newArr = new int[elements.length - 1];
		int element = elements[0];
		for (int i = 0; i < elements.length - 1; i++) {
			newArr[i] = elements[i + 1];
		}
		elements = newArr;
		return element;
	}

	public boolean isEmpty() {
		return elements.length == 0;
	}

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值