java史上最全数据结构与算法
目录:
随着大数据时代的到来,我们现在每天面临着巨大数据量的冲击。一个好的程序数据结构和算法对我们产品和效率是至关重要的,本人通过两个周的复习总结,将其进行总结和归纳,希望能帮到大家。
(1)数据结构与算法概念解析
为何要学算法?
有个字符串”大中中国 中国我爱 中中国我爱 你中国我爱你中国我爱你好”,怎样判断出”中国我爱你”是否存在,如存在则返回位置。非算法思路则是进行暴力匹配,依次匹配,遇到不匹配的再从字符串开头重新依次匹配。但是如果懂KMP算法,则会很简单。
为何要学数据结构?
开发五子棋的存盘、续盘功能,使用二维数组来实现。因为二位数据的很多默认值是0,因为记录了很多无意义的数据,如果优化存储空间就需要稀疏数组,使用二位数组优化为稀疏数组可以大大减少存储空间和提升效率。
以上就是数据结构与算法的重要性,不同的数据结构有不同的算法,算法是为数据结构服务的。
(2)数据结构之数组
数组
特点:我们都知道数组中的元素在内存中连续存储的,可以根据是下标快速访问元素,因此,查询速度很快,然而插入和删除时,需要对元素移动空间,比较慢。
数组使用场景:频繁查询,很少增加和删除的情况。
(3)数据结构之栈
栈
特点:先进后出,就像一个往箱子里放东西和取东西。
使用场景:实现递归以及表示式。
(4)数据结构之队列
队列
特点:先进先出。
使用场景:多线程阻塞队列管理非常有用。
(5)数据结构之链表
链表
特点:元素可以不连续内存中,是以索引将数据联系起来的,当查询元素的时候需要从头开始查询,所以效率比较低,然而添加和删除的只需要修改索引就可以了。
使用场景:少查询,需要频繁的插入或删除情况。
(6)数据结构之二叉树
二叉树的定义:
二叉树是树形结构的一个重要类型。许多实际问题抽象出来的数据结构往往是二叉树的形式,即使是一般的树也能简单地转换为二叉树,而且二叉树的存储结构及其算法都较为简单,因此二叉树显得特别重要。
二叉树(BinaryTree)是n(n≥0)个结点的有限集,它或者是空集(n=0),或者由一个根结点及两棵互不相交的、分别称作这个根的左子树和右子树的二叉树组成。
(7)数据结构之霍夫曼树
霍夫曼树
1、为什么需要霍夫曼树
打个比喻,一篇文章里面每个字母出现的次数不同,有点差距很大。如果在编码的时候把每个字母的编码长度都设为一样,这样当然可行,但是会浪费存储空间。如果存在这样的一种编码,出现次数多的字母编码较少,这样就可以一定长度的节省内存空间。对于这样的编码,我们可以用霍夫曼树来实现
在这里插入图片描述
A—>00
B—>100
C—>101
D—>01
E—>11
这样就会发现这样的编码没有公共的前缀(如果存在公共前缀,那么这个编码是不正确的,以为这样在解码的时候会出现无法判断的情况)
2、霍夫曼树构建过程
(1)从序列里取出两个权值最小的节点
(2)创建一个新的节点,这个新的节点的权值是这两个节点的和
(3)把这个新的节点加入序列
(4)判断这个序列的长度是否大于1,如果是,则回到(1),否则结束构建过程
为了每一次都取出最小,插入的时候都是根据从小到大插入正确的位置,我们创建一个新链表,这个链表是单向的,保存了链表头和元素个数。
(1)插入:判断插入的节点中数据项的大小,然后插入正确位置(这样形成的序列是有序的)
(2)删除:删除表头(表头是最小的元素)
(8)数据结构之红黑树(一)——基础分析
红黑树(R-B Tree),全称是Red-Black Tree,又称为“红黑树”,它一种特殊的二叉查找树。红黑树的每个节点上都有存储位表示节点的颜色,可以是红(Red)或黑(Black)。
红黑树的性质:
(1). 每个节点或者是黑色,或者是红色。
(2). 根节点是黑色。
(3). 每个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!]
(4). 如果一个节点是红色的,则它的子节点必须是黑色的。
(5). 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。
(6). 一棵含有n个节点的红黑树的高度至多为2log(n+1)。
(7). 红黑树的时间复杂度为: O(logn)。
注意:
特性(3)中的叶子节点,是只为空(NIL或null)的节点。
特性(5)确保没有一条路径会比其他路径长出俩倍。因而,红黑树是相对是接近平衡的二叉树。
(9)数据结构之红黑树(二)——插入操作
插入过程也是一个二叉树查找的过程,先看图
在插入节点2之前,该树还是一颗平衡树,当插入节点2之后,节点3就成为了不平衡点,需要对节点3进行左平衡处理。
在详细的分析一下:
首先从根节点4开始搜索,发现2<4,于是搜索4的左节点3,发现2<3,就搜索1,2>1,搜索1的右节点,此时发现1的右节点为空,就执行插入操作,将2插入到1的右节点,那么怎么发现此时这棵树不平衡的呢?因为我们是通过递归寻找插入点,当找到插入点在1的右节点之后,开始往父节点回溯,回溯过程中告诉父节点,孩儿有没有长高,如果长高了,父节点就要判断左右子树高度差是否大于1,也就是处于不平衡状态,每个节点都有个平衡因子,EH=0(等高),LH=1(左边高1),RH=-1(右边高1)例如,插入2之前,节点1的BF=0,节点3BF=1,节点4BF=1,插入节点2之后,往父节点1回溯,说我长高了,1节点BF=0,现在右节点长高了,所以此时节点1BF=-1,在往父节点3回溯,告诉父节点3,我长高了,而父节点3BF=1,而现在左孩子长高了,那BF=2,此时节点3称为不平衡点,需要对节点3做左平衡处理。处理完成后,2节点变成了1,3的父节点,此时2节点的高度和没插入节点2之前3节点高度一样,于是告诉父节点4,我没有长高,此时递归结束。
代码如下:
public void insertAVL(int e){
if(root==null){
root=new Node(e,EH,null,null,null);
return;
}
TS t=new TS();
InsertAVL(root,e,t,null);
}
/* 若在平衡的二叉排序树T中不存在和e有相同关键字的结点,则插入一个 */
/* 数据元素为e的新结点,并返回1,否则返回0。若因插入而使二叉排序树 */
/* 失去平衡,则作平衡旋转处理,布尔变量taller反映T长高与否。 */
private boolean InsertAVL(Node T,int e,TS tl,Node parent)
{
if(T==null)
{
/* 插入新结点,树“长高”,置taller为TRUE */
Node nNode=new Node(e,EH,null,null,parent);
if(e<parent.value)
parent.lchild=nNode;
else
parent.rchild=nNode;
tl.taller=TRUE;
}
else
{
if (e==T.value)
{
/* 树中已存在和e有相同关键字的结点则不再插入 */
tl.taller=FALSE;
return false;
}
else if (e<T.value)
{
/* 应继续在T的左子树中进行搜索 */
if(!InsertAVL(T.lchild,e,tl,T))
return false;
if(tl.taller) /* 已插入到T的左子树中且左子树“长高” */
switch(T.bf) /* 检查T的平衡度 */
{
case LH: /* 原本左子树比右子树高,需要作左平衡处理 */
LeftBalance(T);
tl.taller=FALSE;
break;
case EH: /* 原本左、右子树等高,现因左子树增高而使树增高 */
T.bf=LH;
tl.taller=TRUE;
break;
case RH: /* 原本右子树比左子树高,现左、右子树等高 */
T.bf=EH;
tl.taller=FALSE;
break;
}
}
else
{
/* 应继续在T的右子树中进行搜索 */
if(!InsertAVL(T.rchild,e,tl,T))
return false;
if(tl.taller) /* 已插入到T的右子树且右子树“长高” */
{
switch(T.bf) /* 检查T的平衡度 */
{
case LH: /* 原本左子树比右子树高,现左、右子树等高 */
T.bf=EH;
tl.taller=FALSE;
break;
case EH: /* 原本左、右子树等高,现因右子树增高而使树增高 */
T.bf=RH;
tl.taller=TRUE;
break;
case RH: /* 原本右子树比左子树高,需要作右平衡处理 */
RightBalance(T);
tl.taller=FALSE;
break;
}
}
}
}
return true;
}
(10)数据结构之红黑树(三)——删除操作
删除操作和二叉查找树删除一样,分为三种情况讨论
(1)删除节点没有左子树,这种情况直接将删除节点的父节点指向删除节点的右子树。
(2)删除节点没有右子树,这种情况直接将删除节点的父节点指向删除节点的左子树。
(3)删除节点左右子树都存在,可以采用两种方式,
1:让删除节点左子树的最右侧节点代替当前节点
2:让删除节点右子树的最左侧节点代替当前节点
如下图:
这里的难点是删除之后要判断该树是否还平衡?
还是先看图:
和插入操作一样,删除操作也是递归查找,然后删除,删除之后,该节点A要向父节点回溯,告诉父节点B我变矮了(因为删除了),父节点B此时要判断自己是否也变矮了,如果删除的节点是自己的左子树中的节点(右子树同理,这里只讨论左子树情况,右子树请看代码),就要分三种情况讨论:
(1)B.BF=EH ,也就是原来B节点左右子树高度一致,而现在左子树告诉我,左子树变矮了,则需要将B.BF设置为RH,即右边高,同时可知B的高度并没变化,所以再往B的父节点C回溯的时候,B的父节点C就会当啥都没发生。
(2)B.BF=LH,也就是原来B节点左子树比右子树高一层而现在左子树告诉我,左子树变矮了,则需要将B.BF设置为EH,同时可知B节点的高度也变矮了,于是再往B的父节点C回溯的是否,C也要分三种情况讨论。
(3)B.BF=RH,也就是原来B节点右子树比左子树高一层,而现在左子树告诉我,左子树变矮了,则需要对B进行右平衡处理
而这里又要分为两种情况讨论来判断,右平衡处理完成后,需要判断B的父节点C的左子树是否变矮了
1.B.rchild.BF=EH,也就是B节点(右平衡处理之前)的右子树的左右子树等高。那么这种情况,B的父节点C的左子树不变矮。
2.除了1情况,B的父节点C的左子树会变矮。
下面画图来理解一下这两种情况。
这是第一种情况,6的右节点8的BF=EH,那么旋转后高度不变。
这是第二种情况,6的右节点8的BF!=EH,那么旋转后高度变矮。
其删除的代码如下:
/*
若在平衡的二叉排序树t中存在和e有相同关键字的结点,则删除之
并返回TRUE,否则返回FALSE。若因删除而使二叉排序树
失去平衡,则作平衡旋转处理,布尔变量shorter反映t变矮与否
*/
private boolean deleteAVL(Node t, int key, TS ts)
{
if(t == null) //不存在该元素
{
return FALSE; //删除失败
}
else if(key == t.value) //找到元素结点
{
Node q = null;
if(t.lchild == null) //左子树为空
{
q = t.parent;
if(q==null)
root=t.rchild;
else{
if(key<q.value)
q.lchild=t.rchild;
else
q.rchild=t.rchild;
}
ts.shorter = TRUE;
}
else if(t.rchild == null) //右子树为空
{
q = t.parent;
if(q==null)
root=t.lchild;
else{
if(key<q.value)
q.lchild=t.lchild;
else
q.rchild=t.lchild;
}
ts.shorter = TRUE;
}
else //左右子树都存在,
{
q = t.lchild;
while(q.rchild!=null)
{
q = q.rchild;
}
t.value = q.value;
deleteAVL(t.lchild, q.value, ts); //在左子树中递归删除前驱结点
}
}
else if(key < t.value) //左子树中继续查找
{
if(!deleteAVL(t.lchild, key, ts))
{
return FALSE;
}
if(ts.shorter)
{
switch(t.bf)
{
case LH:
t.bf = EH;
ts.shorter = TRUE;
break;
case EH:
t.bf = RH;
ts.shorter = FALSE;
break;
case RH:
if(t.rchild.bf == EH) //注意这里,画图思考一下
ts.shorter = FALSE;
else
ts.shorter = TRUE;
RightBalance(t); //右平衡处理
break;
}
}
}
else //右子树中继续查找
{
if(!deleteAVL(t.rchild, key, ts))
{
return FALSE;
}
if(ts.shorter)
{
switch(t.bf)
{
case LH:
if(t.lchild.bf == EH) //注意这里,画图思考一下
ts.shorter = FALSE;
else
ts.shorter = TRUE;
LeftBalance(t); //左平衡处理
break;
case EH:
t.bf = LH;
ts.shorter = FALSE;
break;
case RH:
t.bf = EH;
ts.shorter = TRUE;
break;
}
}
}
return TRUE;
}
(11)排序算法(一)——冒泡排序及改进
/**
* 冒泡排序
*
* @return
*/
public static int[] sortBubble(int[] datas) {
for (int i = 0; i < datas.length - 1; i++) {
for (int j = 0; j < datas.length - 1 - i; j++) {
if (datas[j] > datas[j + 1])
AlgorithmUtil.swap(datas, j, j + 1);
}
}
return datas;
}
(12)排序算法(二)——选择排序及改进
/**
* 选择排序
*
* @return
*/
public static int[] sortSelect(int[] datas) {
for (int i = 0; i < datas.length; i++) {
int index = i;
for (int j = i + 1; j < datas.length; j++) {
if (datas[j] < datas[index])
index = j;
}
if (i != index)
AlgorithmUtil.swap(datas, i, index);
}
return datas;
}
## (13)排序算法(三)——插入排序及改进
/**
* ClassName:InsertSort <br/>
*
* Java的插入排序
* @author Administrator
* @version
* @since JDK 1.8
* @see
*/
public class InsertSort {
/**
* 排序算法的实现,对数组中指定的元素进行排序
*
* @param array 待排序的数组
* @param from 从哪里开始排序
* @param end 排到哪里
* @param c 比较器
*/
public void insert(Integer[] array, int from, int end) {
/*
* 第一层循环:对待插入(排序)的元素进行循环 从待排序数组断的第二个元素开始循环,到最后一个元素(包括)止
*/
for (int i = from + 1; i <= end; i++) {
/*
* 第二层循环:对有序数组进行循环,且从有序数组最第一个元素开始向后循环 找到第一个大于待插入的元素
* 有序数组初始元素只有一个,且为源数组的第一个元素,一个元素数组总是有序的
*/
for (int j = 0; j < i; j++) {
Integer insertedElem = array[i];// 待插入到有序数组的元素
// 从有序数组中最一个元素开始查找第一个大于待插入的元素
if ((array[j].compareTo(insertedElem)) > 0) {
// 找到插入点后,从插入点开始向后所有元素后移一位
move(array, j, i - 1);
// 将待排序元素插入到有序数组中
array[j] = insertedElem;
break;
}
}
}
// =======以下是java.util.Arrays的插入排序算法的实现
/*
* 该算法看起来比较简洁一j点,有点像冒泡算法。 将数组逻辑上分成前后两个集合,前面的集合是已经排序好序的元素,而后面集合为待排序的
* 集合,每次内层循从后面集合中拿出一个元素,通过冒泡的形式,从前面集合最后一个元素开 始往前比较,如果发现前面元素大于后面元素,则交换,否则循环退出
*
* 总感觉这种算术有点怪怪,既然是插入排序,应该是先找到插入点,而后再将待排序的元素插
* 入到的插入点上,那么其他元素就必然向后移,感觉算法与排序名称不匹,但返过来与上面实
* 现比,其实是一样的,只是上面先找插入点,待找到后一次性将大的元素向后移,而该算法却 是走一步看一步,一步一步将待排序元素往前移
*/
/*
* for (int i = from; i <= end; i++) { for (int j = i; j > from &&
* c.compare(array[j - 1], array[j]) > 0; j--) { swap(array, j, j - 1); } }
*/
}
/**
* 数组元素后移
*
* @param array 待移动的数组
* @param startIndex 从哪个开始移
* @param endIndex 到哪个元素止
*/
public void move(Integer[] array, int startIndex, int endIndex) {
for (int i = endIndex; i >= startIndex; i--) {
array[i + 1] = array[i];
}
}
public static void main(String[] args) {
Integer[] intgArr = { 3, 1, 6, 2, 5 };
InsertSort insertSort = new InsertSort();
insertSort.insert(intgArr, 0, intgArr.length - 1);
for (Integer intObj : intgArr) {
System.out.print(intObj + " ");
}
}
}
(14)排序算法(四)——归并排序与递归
/**
* 归并排序递归版
* @author liguodong
*/
public class Demo02 {
public static void mergeSort(int[] a){
mSort(a, a, 0, a.length-1);
}
/**
*
* @param SR为待排序的数据
* @param TR1为排序之后的数据
* @param s
* @param t
*/
public static void mSort(int[] SR,int[] TR1, int s,int t){
int m;
int[] TR2 = new int[SR.length];
if(s==t){
TR1[s] = SR[s];
}else {
m = (s+t)/2;//4
mSort(SR, TR2, s, m);
mSort(SR, TR2, m+1, t);
merge(TR2, TR1, s, m, t);//0 4 8
}
}
//归并两个有序的数组
/**
* @param SR 有两个有序数组
* @param TR 将SR的两个有序数组合并为一个数组TR
* @param i
* @param m
* @param n
*/
public static void merge(int[] SR,int[] TR,int i,int m,int n){
int j,k,l;
//i(0~4) j(5~8)
for(j=m+1,k=i; i<=m && j<=n; k++){
if(SR[i]<SR[j]){
TR[k] = SR[i++];
}else{
TR[k] = SR[j++];
}
}
if(i<=m){
for (l = 0; l <= m-i ; l++) {
TR[k+l] = SR[i+l];
}
}
if(j<=n){
for (l = 0; l <= n-j; l++) {
TR[k+l] = SR[j+l];
}
}
}
public static void main(String[] args) {
int[] a = {2,3,5,4,1,6,9,8,7};
mergeSort(a);
SortUtils.printString(a);
}
}
(15)排序算法(五)——快速排序
/**
*Java的快速排序
*
/
public class QuickSort {
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.println(arr[i]);
}
}
}
(16)排序算法(六)——希尔排序
//希尔排序
public static void shellSort(int[] arr){
//inc是增量
int temp = 0;
int j = 0;
//增量默认是长度的一半,每次变为之前的一半,直到最终数组有序
for(int inc=arr.length/2 ; inc>=1 ; inc/=2){
for(int i=inc ; i<arr.length; i++){
temp = arr[i];
//将当前的数与减去增量之后位置的数进行比较,如果大于当前数,将他后移
for(j=i-inc; j>=0;j-=inc){
if(arr[j]>temp){
arr[j+inc] = arr[j];
}else{
break;
}
}
//将当前数放到空出来的位置
arr[j+inc]=temp;
}
}
System.out.println(Arrays.toString(arr));
}
//测试代码
public static void main(String[] args){
int[] arr = {1,3,4,2,123,12};
shellSort(arr);
}
(17)排序算法(七)——堆排序
堆排序:堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
/**
* Java排序算法之堆排序
* @author Administrator
*
*/
public class HeapSort {
public static void main(String []args){
int []arr = {7,6,7,11,5,12,3,0,1};
System.out.println("排序前:"+Arrays.toString(arr));
sort(arr);
System.out.println("排序前:"+Arrays.toString(arr));
}
public static void sort(int []arr){
//1.构建大顶堆
for(int i=arr.length/2-1;i>=0;i--){
//从第一个非叶子结点从下至上,从右至左调整结构
adjustHeap(arr,i,arr.length);
}
//2.调整堆结构+交换堆顶元素与末尾元素
for(int j=arr.length-1;j>0;j--){
swap(arr,0,j);//将堆顶元素与末尾元素进行交换
adjustHeap(arr,0,j);//重新对堆进行调整
}
}
/**
* 调整大顶堆(仅是调整过程,建立在大顶堆已构建的基础上)
* @param arr
* @param i
* @param length
*/
public static void adjustHeap(int []arr,int i,int length){
int temp = arr[i];//先取出当前元素i
for(int k=i*2+1;k<length;k=k*2+1){//从i结点的左子结点开始,也就是2i+1处开始
if(k+1<length && arr[k]<arr[k+1]){//如果左子结点小于右子结点,k指向右子结点
k++;
}
if(arr[k] >temp){//如果子节点大于父节点,将子节点值赋给父节点(不用进行交换)
arr[i] = arr[k];
i = k;
}else{
break;
}
}
arr[i] = temp;//将temp值放到最终的位置
}
/**
* 交换元素
* @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;
}
}
(18)排序算法(八)——基数排序
基数排序也是排序算法的一种,老规矩,先来看看百度百科的定义:基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或bin sort,顾名思义,它是透过键值的部份资讯,将要排序的元素分配至某些“桶”中,藉以达到排序的作用,基数排序法是属于稳定性的排序,其时间复杂度为O (nlog®m),其中r为所采取的基数,而m为堆数,在某些时候,基数排序法的效率高于其它的稳定性排序法。
//Java的基数排序
class Demo
{
public static void main(String[] args)
{
//定义整型数组
int[] arr = {21,56,88,195,354,1,35,12,6,7};
//调用基数排序函数
lsd_RadixSort(arr,3);
//输出排序后的数组
for(int i=0;i<arr.length;i++)
{
System.out.print(arr[i]+" ");
}
}
//arr是要排序的数组,max是数组中最大的数有几位
public static void lsd_RadixSort(int[] arr,int max)
{
//count数组用来计数
int[] count = new int[arr.length];
//bucket用来当桶(在下面你就理解了什么是桶了),放数据,取数据
int[] bucket = new int[arr.length];
//k表示第几位,1代表个位,2代表十位,3代表百位
for(int k=1;k<=max;k++)
{
//把count置空,防止上次循环的数据影响
for(int i=0;i<arr.length;i++)
{
count[i] = 0;
}
//分别统计第k位是0,1,2,3,4,5,6,7,8,9的数量
//以下便称为桶
//即此循环用来统计每个桶中的数据的数量
for(int i=0;i<arr.length;i++)
{
count[getFigure(arr[i],k)]++;
}
//利用count[i]来确定放置数据的位置
for(int i=1;i<arr.length;i++)
{
count[i] = count[i] + count[i-1];
}
//执行完此循环之后的count[i]就是第i个桶右边界的位置
//利用循环把数据装入各个桶中,注意是从后往前装
//这里是重点,一定要仔细理解
for(int i=arr.length-1;i>=0;i--)
{
int j = getFigure(arr[i],k);
bucket[count[j]-1] = arr[i];
count[j]--;
}
//将桶中的数据取出来,赋值给arr
for(int i=0,j=0;i<arr.length;i++,j++)
{
arr[i] = bucket[j];
}
}
}
//此函数返回整型数i的第k位是什么
public static int getFigure(int i,int k)
{
int[] a = {1,10,100};
return (i/a[k-1])%10;
}
}
(19)排序算法(九)——八大排序算法总结
一. 冒泡排序(BubbleSort)
基本思想:两个数比较大小,较大的数下沉,较小的数冒起来。
过程:
-
比较相邻的两个数据,如果第二个数小,就交换位置。
-
从后向前两两比较,一直到比较最前两个数据。最终最小数被交换到起始的位置,这样第一个最小数的位置就排好了。
-
继续重复上述过程,依次将第2.3…n-1个最小数排好位置。
冒泡排序
平均时间复杂度:O(n2)
优化:
针对问题:
数据的顺序排好之后,冒泡算法仍然会继续进行下一轮的比较,直到arr.length-1次,后面的比较没有意义的。
方案:
设置标志位flag,如果发生了交换flag设置为true;如果没有交换就设置为false。
这样当一轮比较结束后如果flag仍为false,即:这一轮没有发生交换,说明数据的顺序已经排好,没有必要继续进行下去。
二. 选择排序(SelctionSort)
基本思想:
在长度为N的无序数组中,第一次遍历n-1个数,找到最小的数值与第一个元素交换;
第二次遍历n-2个数,找到最小的数值与第二个元素交换;
。。。
第n-1次遍历,找到最小的数值与第n-1个元素交换,排序完成。
过程:
选择排序
平均时间复杂度:O(n2)
三. 插入排序(Insertion Sort)
基本思想:
在要排序的一组数中,假定前n-1个数已经排好序,现在将第n个数插到前面的有序数列中,使得这n个数也是排好顺序的。如此反复循环,直到全部排好顺序。
过程:
平均时间复杂度:O(n2)
四. 希尔排序(Shell Sort)
前言:
数据序列1: 13-17-20-42-28 利用插入排序,13-17-20-28-42. Number of swap:1;
数据序列2: 13-17-20-42-14 利用插入排序,13-14-17-20-42. Number of swap:3;
如果数据序列基本有序,使用插入排序会更加高效。
基本思想:
在要排序的一组数中,根据某一增量分为若干子序列,并对子序列分别进行插入排序。
然后逐渐将增量减小,并重复上述过程。直至增量为1,此时数据序列基本有序,最后进行插入排序。
过程:
五. 快速排序(Quicksort)
基本思想:(分治)
先从数列中取出一个数作为key值;
将比这个数小的数全部放在它的左边,大于或等于它的数全部放在它的右边;
对左右两个小数列重复第二步,直至各区间只有1个数。
辅助理解:挖坑填数
初始时 i = 0; j = 9; key=72
由于已经将a[0]中的数保存到key中,可以理解成在数组a[0]上挖了个坑,可以将其它数据填充到这来。
从j开始向前找一个比key小的数。当j=8,符合条件,a[0] = a[8] ; i++ ; 将a[8]挖出再填到上一个坑a[0]中。
这样一个坑a[0]就被搞定了,但又形成了一个新坑a[8],这怎么办了?简单,再找数字来填a[8]这个坑。
这次从i开始向后找一个大于key的数,当i=3,符合条件,a[8] = a[3] ; j-- ; 将a[3]挖出再填到上一个坑中。
此时 i = 3; j = 7; key=72
再重复上面的步骤,先从后向前找,再从前向后找。
从j开始向前找,当j=5,符合条件,将a[5]挖出填到上一个坑中,a[3] = a[5]; i++;
从i开始向后找,当i=5时,由于i==j退出。
此时,i = j = 5,而a[5]刚好又是上次挖的坑,因此将key填入a[5]。
可以看出a[5]前面的数字都小于它,a[5]后面的数字都大于它。因此再对a[0…4]和a[6…9]这二个子区间重复上述步骤就可以了。
平均时间复杂度:O(N*logN)
六. 归并排序(Merge Sort)
基本思想:参考
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法的一个非常典型的应用。
首先考虑下如何将2个有序数列合并。这个非常简单,只要从比较2个数列的第一个数,谁小就先取谁,取了后就在对应数列中删除这个数。然后再进行比较,如果有数列为空,那直接将另一个数列的数据依次取出即可。
过程:
归并排序
平均时间复杂度:O(NlogN)
归并排序的效率是比较高的,设数列长为N,将数列分开成小数列一共要logN步,每步都是一个合并有序数列的过程,时间复杂度可以记为O(N),故一共为O(N*logN)。
七. 堆排序(HeapSort)
基本思想:
平均时间复杂度:O(NlogN)
由于每次重新恢复堆的时间复杂度为O(logN),共N - 1次重新恢复堆操作,再加上前面建立堆时N / 2次向下调整,每次调整时间复杂度也为O(logN)。二次操作时间相加还是O(N * logN)。
八. 基数排序(RadixSort)
BinSort
基本思想:
BinSort想法非常简单,首先创建数组A[MaxValue];然后将每个数放到相应的位置上(例如17放在下标17的数组位置);最后遍历数组,即为排序后的结果。
图示:
BinSort
问题: 当序列中存在较大值时,BinSort 的排序方法会浪费大量的空间开销。
RadixSort
基本思想: 基数排序是在BinSort的基础上,通过基数的限制来减少空间的开销。
过程:
(1)首先确定基数为10,数组的长度也就是10.每个数34都会在这10个数中寻找自己的位置。
(2)不同于BinSort会直接将数34放在数组的下标34处,基数排序是将34分开为3和4,第一轮排序根据最末位放在数组的下标4处,第二轮排序根据倒数第二位放在数组的下标3处,然后遍历数组即可。