引言
相信作为科班出生的同学都已经学习过了数据结构,可作为App开发者或者Web开发者,又有多少真正的灵活运用数据结构来解决自己的时机问题呢,数据结构或许在一些开发者的眼里是可有可无的东西,不用数据结构也能解决,其实并不然,很多时候追求高质量代码的过程,数据结构或许是一个有力的帮手,数据结构对于普通非算法工程师来说,其思想比实现更重要,仅个人拙见。
一、数据结构与算法概述
1、数据结构
数据结构是指相互之间存在着一种或多种关系的数据元素的集合和该集合中数据元素之间的关系组成。而数据结构又由逻辑结构和存储结构构成,逻辑结构指反映数据元素之间的逻辑关系的数据结构,其中的逻辑关系是指数据元素之间的前后件关系,而与他们在计算机中的存储位置无关,主要又分为:
- 集合—— 数据结构中的元素之间除了“同属一个集合” 的相互关系外,别无其他关系
- 线性结构 ——数据结构中的元素存在一对一的相互关系
- 树形结构—— 数据结构中的元素存在一对多的相互关系
- 图形结构 ——数据结构中的元素存在多对多的相互关系
而存储结构是指在计算机存储空间的存放形式。即数据存储的物理结构在计算机中的表示(又称映像),由于具体实现的方法有顺序、链接、索引、散列等多种,所以,一种数据结构可表示成一种或多种存储结构。常用的数据结构有数组、栈、队列、链表、树、二叉树、图、散列表等。
2 、算法
算法是指解题方案的准确而完整的描述,是一系列解决问题的清晰指令,算法代表着用系统的方法描述解决问题的策略机制,它是求解问题类的、机械的、统一的方法,常用于计算、数据处理和自动推理,可以理解为有基本运算及规定的运算顺序所构成的完整的解题步骤。简而言之,算法就是解决问题的一套方法论。通常算法可大致分为基本算法、数据结构的算法、数论与代数算法、计算几何的算法、图论的算法、动态规划以及数值分析、加密算法、排序算法、检索算法、随机化算法、并行算法等。一个算法的优劣是综合时间复杂度、空间复杂度和应用场景来考虑,鱼和熊掌不可兼得。
-
时间复杂度
算法的时间复杂度是指执行关键操作所需要的次数。一般来说,计算机算法是问题规模n 的函数f(n),算法的时间复杂度记为T(n)=Ο(f(n))因此问题的规模n 越大,算法执行的时间的增长率与f(n) 的增长率正相关 -
空间复杂度
算法的空间复杂度是指算法需要消耗的内存空间。其计算和表示方法与时间复杂度类似,一般都用复杂度的渐近性来表示。
二、线性表概述
线性表是最简单、最基本、也是最常用的一种线性结构。 线性表是具有相同数据类型的n(n>=0)个数据元素的有限序列,通常记为: (a1,a2,… ai-1,ai,ai+1,…an) ,其中n为表长, n=0 时称为空表。 它有两种存储方法:顺序存储和链式存储()它的主要基本操作是插入、删除和检索等。
1、顺序存储
顺序存储是把逻辑上相邻的结点存储在物理位置相邻的存储单元里,结点间的逻辑关系由存储单元的邻接关系来体现,元素存放的位置与放入的时间有关,即存储内存空间连续,典型代表如数组、ArrayList等。
public class ArrayList<E> extends AbstractList<E>implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
...
private static final int DEFAULT_CAPACITY = 10;
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
transient Object[] elementData; // non-private to simplify nested class access
private int size;
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
//创建一个空数组
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
public void ensureCapacity(int minCapacity) {
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
// any size if not default element table
? 0
// larger than default for default empty table. It's already
// supposed to be at default size.
: DEFAULT_CAPACITY;
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
public int lastIndexOf(Object o) {
if (o == null) {
for (int i = size-1; i >= 0; i--)
if (elementData[i]==null)
return i;
} else {
for (int i = size-1; i >= 0; i--)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
public boolean add(E e) {
//
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
public void add(int index, E element) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
...
}
从ArrayList的源码中得知,操作顺序存储线性表本质上其实就是内部操作数组,ArrayList中核心部分就是自动扩容的思想:在添加元素到elementData时,首先把如果当前已经存储的元素个数size+1传入进行检测当前容量是否充足,若充足则直接添加数组中,反之则按照原来容量的1.5 倍进行扩容,扩容完毕之后再把原来的数据赋值到新数组中,而其他删除、检索本质就是操作数组。
2、链式存储
链式存储不要求逻辑上相邻的结点在物理位置上亦相邻,节点间的逻辑关系是由附加的指针字段表示的,即存储内存空间不一定连续。简而言之,各节点之间通过持有引用而在逻辑上链接起来。相比于顺序存储,链式存储增删效率更高,性能也更好,但是顺序存储的读取效率更高。
详见下篇文章。
三、冒泡排序
冒泡排序(适用于3 ~~ 5个数据的排序,性能最好,7 ~~ 8个也还可以),事实上很多五张以内的棋牌游戏排序就是冒泡排序。
1、算法核心思想
相邻的两个数据依次遍历相比,不同则交互位置继续往下比较,如此循环反复直到没有任何一对数字需要比较,如下图是按照升序排列进行排序的过程:
2、实现
此处我采用的是策略模式封装的两种泛型排序方法。
package sort;
import java.util.Comparator;
/**
* 统一排序基类
* @author Crazy.Mo
*
*/
public abstract class AbsSort {
protected boolean isDesc=false;
/**
* 实现了Comparable接口的对象数组排序,比如对象实例k.compareTo(y)
* 如果k等于参数对象y,则返回值 0;
如果k大于参数对象y,则返回一个大于 0 的值;
如果k小于参数对象y,则返回一个小于 0 的值。
* @param arry
*/
public abstract <T extends Comparable<T>> void sort(T[] arry);
public abstract <T> void sort(T[] arry,Comparator<T> comparator);
public void setIsDesc(boolean isdesc){
this.isDesc=isdesc;
}
}
冒泡排序的实现
package sort;
import java.util.Comparator;
/**
* 冒泡排序(适用于3~5个数据的排序,性能最好,7~8个也还可以),事实上很多五张以内的棋牌游戏排序就是冒泡排序
* 时间复杂度为O(N)=n*(n-1)/2 即为 n^2
* 核心思想:相邻的两个数据依次遍历相比,不同则交互位置继续往下比较
* @author Crazy.Mo
*
*/
public class BubbleSort extends AbsSort {
public <T extends Comparable<T>> void sort(T[] arry) {
boolean isBubble=true;//为了尽量优化性能避免不必要的循环为做的标志位
for(int i=arry.length-1;i>0;i--){
for(int j=0;j<i;j++){
if(isDesc){
T temp=arry[j];
if(temp.compareTo(arry[j+1])<0){
temp=arry[j];
arry[j]=arry[j+1];
arry[j+1]=temp;
isBubble=false;
}
}else{
T temp=arry[j];
if(temp.compareTo(arry[j+1])>0){
temp=arry[j];
arry[j]=arry[j+1];
arry[j+1]=temp;
isBubble=false;
}
}
}
if(isBubble){
break;
}
}
}
public <T> void sort(T[] arry, Comparator<T> comparator) {
boolean isBubble=true;
for(int i=arry.length-1;i>0;i--){
for(int j=0;j<i;j++){
if(isDesc){
T temp=arry[j];
if(comparator.compare(temp,(arry[j+1]))<0){
arry[j]=arry[j+1];
arry[j+1]=temp;
isBubble=false;
}
}else{
T temp=arry[j];
if(comparator.compare(temp,(arry[j+1]))>0){
arry[j]=arry[j+1];
arry[j+1]=temp;
isBubble=false;
}
}
}
if(isBubble){
break;
}
}
}
}
测试
package sort;
import java.util.Comparator;
public class Sample {
@SuppressWarnings("unchecked")
public static void main(String[] args) {
AbsSort sort = new SelectSort();
Cards[] array = new Cards[] { new Cards(3, 2), new Cards(2, 9),
new Cards(1, 7), new Cards(3, 5), new Cards(4, 3) };
sort.setIsDesc(true);
//sort.sort(array);
sort.sort(array, new Comparator<Cards>() {
public int compare(Cards o1, Cards o2) {
if (o1.cardPoints > o2.cardPoints) {
return 1;
}
return -1;
}
});
for (Cards cards : array) {
System.out.println(cards.toString());
}
}
public static class Cards implements Comparable {
public int pokerColors;// 花色
public int cardPoints;// 点数
public Cards(int pokerColors, int cardPoints) {
this.pokerColors = pokerColors;
this.cardPoints = cardPoints;
}
// 提供一个方法,用来比较对象的大小
public int compareTo(Object o) {
Cards c = (Cards) o;
if (this.cardPoints > c.cardPoints) {
return 1;
} else if (this.cardPoints < c.cardPoints) {
return -1;
}
if (this.pokerColors > c.pokerColors) {
return 1;
} else if (this.pokerColors < c.pokerColors) {
return -1;
}
return 0;
}
@Override
public String toString() {
return "Cards{" + "pokerColors=" + pokerColors + ", cardPoints="
+ cardPoints + '}';
}
}
}
四、选择排序
1、算法核心思想
内层循环每一次找到第k小(大)的值,如果符合条件则与a[k]交换。
2、实现
package sort;
import java.util.Comparator;
/**
* 选择排序(适用于十个数据的排序,性能最好),事实上很多十张以内的棋牌游戏排序就是选择排序 时间复杂度为O(N)=n*(n-1)/2
* 核心思想:每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
* 以此类推,直到全部待排序的数据元素排完。 选择排序是不稳定的排序方法。
* @author Crazy.Mo
*
*/
public class SelectSort extends AbsSort {
@Override
public <T extends Comparable<T>> void sort(T[] arry) {
for (int i = 0; i < arry.length - 1; i++) {
int index = i;//保存最值的索引
for (int j = i + 1; j < arry.length; j++) {
if (isDesc) {
if (arry[j].compareTo(arry[index]) > 0) {
index = j;
}
} else {
if (arry[j].compareTo(arry[index]) < 0) {
index = j;
}
}
}
// {1,2,5,8,3,9,4,6,7};
if (index != i) {// 如果已经是最小的,就不需要交换
T temp = arry[index];
arry[index] = arry[i];
arry[i] = temp;
}
}
}
@Override
public <T> void sort(T[] arry, Comparator<T> comparator) {
for (int i = 0; i < arry.length - 1; i++) {
int index = i;
for (int j = i + 1; j < arry.length; j++) {
if (isDesc) {
if (comparator.compare(arry[j], (arry[index])) > 0) {
index = j;
}
} else {
if (comparator.compare(arry[j], (arry[index])) < 0) {
index = j;
}
}
}
if (index != i) {// 如果已经是最小的,就不需要交换
T temp = arry[index];
arry[index] = arry[i];
arry[i] = temp;
}
}
}
}