在编程过程当中,一般会遇到的一个问题就是,性能瓶颈。不少时候考虑的都是怎么去作横向扩展,但恰恰忽略掉了最基本的问题就是系统是否真的已经达到了瓶颈?
性能瓶颈一般的表象是资源消耗过多外部处理系统的性能不足;或者资源消耗很少但程序的响应速度却仍达不到要求。
而调优的方式就是 寻找过分消耗资源的代码 和 寻找未充分使用资源但程序执行慢的缘由和代码。 基础决定高度 就拿汽车来比较,一般不懂变速箱、发动机的原理但也是能开车,一样一个不懂数据结构和算法的人也能编程。不少人以为基本的数据结构及操做已经在高级语言中封装,均可以直接调用库函数,那么到底有没有必要好好学习数据结构?java
数据结构+算法=程序
node
一般在程序中,遇到一个实际问题,充分利用数据结构,将数据及其之间的关系有效地存储在计算机中,而后选择合适的算法策略,并用程序高效实现,这才是提升程序性能的主要方式。git
若是没有具有这块相应的知识,怎么完成上述的实现?若是脱离了原有的调用,怎么完成程序的高效实现?而全部的应用实现都依赖于基础,基础就是数据结构和算法。了解这块,才能作到无惧任何技术的演变。全部说基础决定高度!github
数据结构表示数据在计算机中的存储和组织形式,主要描述数据元素之间和位置关系等。选择适当的数据结构能够提升计算机程序的运行效率(时间复杂度)和存储效率(空间复杂度)。算法
数据结构的三种层次
:编程
集合结构(集)
: 全部的元素都属于一个整体,除了同属于一个集合外没有其余关系。集合结构不强调元素之间的任何关联性。线性结构(表)
: 数据元素之间具备一对一的先后关系。结构中必须存在惟一的首元素和惟一的尾元素。树形结构(树)
: 数据元素之间一对多的关系网状结构(图)
: 图状结构或网状结构 结构中的数据元素之间存在多对多的关系顺序结构
: 顺序结构就是使用一组连续的存储单元依次存储逻辑上相邻的各个元素
优势
: 只须要申请存放数据自己的内存空间便可,支持下标访问,也能够实现随机访问。
缺点
: 必须静态分配连续空间,内存空间的利用率比较低。插入或删除可能须要移动大量元素,效率比较低数组
链式结构
: 链式存储结构不使用连续的存储空间存放结构的元素,而是为每个元素构造一个节点。节点中除了存放数据自己之外,还须要存放指向下一个节点的指针。
优势
: 不采用连续的存储空间致使内存空间利用率比较高,克服顺序存储结构中预知元素个数的缺点 插入或删除元素时,不须要移动大量的元素。
缺点
: 须要额外的空间来表达数据之间的逻辑关系, 不支持下标访问和随机访问。数据结构
索引结构
: 除创建存储节点信息外,还创建附加的索引表来标节点的地址。索引表由若干索引项组成。
优势
: 是用节点的索引号来肯定结点存储地址,检索速度块
`缺点: 增长了附加的索引表,会占用较多的存储空间。app
散列结构
: 由节点的关键码值决定节点的存储地址。散列技术除了能够用于查找外,还能够用于存储。
优势
: 散列是数组存储方式的一种发展,采用存储数组中内容的部分元素做为映射函数的输入,映射函数的输出就是存储数据的位置, 相比数组,散列的数据访问速度要高于数组
缺点
: 不支持排序,通常比用线性表存储须要更多的空间,而且记录的关键字不能重复。数据结构和算法
数据结构比较
经常使用的数据结构:
数据结构选择:
O符号
O在算法当中表述的是时间复杂度,它在分析算法复杂性的方面很是有用。常见的有:
O(1)
:最低的复杂度,不管数据量大小,耗时都不变,均可以在一次计算后得到。哈希算法就是典型的O(1)O(n)
:线性,n表示数据的量,当量增大,耗时也增大,常见有遍历算法O(n²)
:平方,表示耗时是n的平方倍,当看到循环嵌循环的时候,基本上这个算法就是平方级的,如:冒泡排序等O(log n)
:对数,一般ax=n,那么数x叫作以a为底n的对数,也就是x=logan,这里是a一般是2,如:数量增大8倍,耗时只增长了3倍,二分查找就是对数级的算法,每次剔除一半O(n log n)
:线性对数,就是n乘以log n,按照上面说的数据增大8倍,耗时就是8*3=24倍,归并排序就是线性对数级的算法在Java中,数组是用来存放同一种数据类型的集合,注意只能存放同一种数据类型。
//只声明了类型和长度
数据类型 [] 数组名称 = new 数据类型[数组长度];
//声明了类型,初始化赋值,大小由元素个数决定
数据类型 [] 数组名称 = {数组元素1,数组元素2,......}
复制代码
大小固定,不能动态扩展(初始化给大了,浪费;给小了,不够用),插入快,删除和查找慢
public class Array {
private int[] intArray;
private int elems;
private int length;
public Array(int max) {
length = max;
intArray = new int[max];
elems = 0;
}
/** * 添加 * @param value */
public void add(int value){
if(elems == length){
System.out.println("error");
return;
}
intArray[elems] = value;
elems++;
}
/** * 查找 * @param searchKey * @return */
public int find(int searchKey){
int i;
for(i = 0; i < elems; i++){
if(intArray[i] == searchKey)
break;
}
if(i == elems){
return -1;
}
return i;
}
/** * 删除 * @param value * @return */
public boolean delete(int value){
int i = find(value);
if(i == -1){
return false;
}
for (int j = i; j < elems-1; j++) {
//后面的数据往前移
intArray[j] = intArray[j + 1];
}
elems--;
return true;
}
/** * 更新 * @param oldValue * @param newValue * @return */
public boolean update(int oldValue,int newValue){
int i = find(oldValue);
if(i == -1){
return false;
}
intArray[i] = newValue;
return true;
}
/** * 显示全部 */
public void display(){
for(int i = 0 ; i < elems ; i++){
System.out.print(intArray[i]+" ");
}
System.out.print("\n");
}
/** * 冒泡排序 * 每趟冒出一个最大数/最小数 * 每次运行数量:总数量-运行的趟数(已冒出) */
public void bubbleSort(){
for(int i = 0; i < elems-1; i++){//排序趟数 n-1次就好了
System.out.println("第"+(i+1)+"趟:");
for(int j = 0; j < elems-i-1; j++){//每趟次数 (n-i) -1是防止下标越界,后面赋值用到了+1
if(intArray[j] > intArray[j+1]){ //控制按大冒泡仍是按小冒泡
int temp = intArray[j];
intArray[j] = intArray[j+1];
intArray[j+1] = temp;
}
display();
}
}
}
/*** * 选择排序 * 每趟选择一个最大数/最小数 * 每次运行数量:总数量-运行的趟数(已选出) */
public void selectSort(){
for(int i = 0; i < elems-1; i++) {//排序趟数 n-1次就好了
int min = i;
for(int j = i+1; j < elems; j++){ //排序次数 每趟选择一个数出来,-1次
if(intArray[j] < intArray[min]){
min = j;
}
}
if(i != min ){
int temp = intArray[i];
intArray[i] = intArray[min];
intArray[min] = temp;
}
display();
}
}
/** * 插入排序 * 每趟选择一个待插入的数 * 每次运行把待插入的数放在比它大/小后面 */
public void insertSort(){
int j;
for(int i = 1; i < elems; i++){
int temp = intArray[i];
j = i;
while (j > 0 && temp < intArray[j-1]){
intArray[j] = intArray[j-1];
j--;
}
intArray[j] = temp;
}
display();
}
public static void main(String[] args) {
Array array = new Array(10);
array.add(6);
array.add(3);
array.add(8);
array.add(2);
array.add(11);
array.add(5);
array.add(7);
array.add(4);
array.add(9);
array.add(10);
// array.bubbleSort();
// array.selectSort();
array.insertSort();
// array.display();
// System.out.println(array.find(4));
// System.out.println(array.delete(1));
// array.display();
// System.out.println(array.update(2,6));
// array.display();
}
}
复制代码
O(n)
O(n)
O(1)
O(1)
Stack()
复制代码
public class Stack {
//小贴士:一般能够利用栈实现字符串逆序,还能够利用栈判断分隔符是否匹配,如<a[b{c}]>,能够正进反出,栈为空则成功。
/**基于数组实现的顺序栈,连续存储的线性实现,须要初始化容量**/
//固定数据类型
//private int[] array;
//动态数据类型
private Object[] objArray;
private int maxSize;
private int top;
public Stack() {
}
public Stack(int maxSize) {
if(maxSize > 0){
objArray = new Object[maxSize];
this.maxSize = maxSize;
top = -1;
}else{
throw new RuntimeException("初始化大小错误:maxSize=" + maxSize);
}
}
/** * 入栈 * @param obj */
public void objPush(Object obj){
//扩容
grow();
//++在前表示先运算再赋值,优先级高,在后表示先赋值再运算,优先级低
objArray[++top] = obj;
}
/** * 出栈 * @return */
public Object objPop(){
Object obj = peekTop();
//声明原顶栈能够回收空间(GC)
objArray[top--] = null;
return obj;
}
/** * 查看栈顶 * @return */
public Object peekTop(){
if(top != -1){
return objArray[top];
}else{
throw new RuntimeException("stack is null");
}
}
/** * 动态扩容 */
public void grow(){
// << 左移运算符,1表示乘以2的1次方
if(top == maxSize-1){
maxSize = maxSize<<1;
objArray = Arrays.copyOf(objArray,maxSize);
}
}
/**基于链式存储,不连续存储的非线性实现**/
private class Node<Object>{
private Object data;
private Node next;
public Node(Object data, Node next) {
this.data = data;
this.next = next;
}
}
private Node nodeTop;
private int size;
public void nodePush(Object obj){
//栈顶指向新元素,新元素的next指向原栈顶元素
nodeTop = new Node(obj,nodeTop);
size++;
}
public Object nodePop(){
Node old = nodeTop;
//声明原顶栈能够回收空间(GC)
old.next = null;
//栈顶指向下一个元素
nodeTop = nodeTop.next;
size--;
return old.data;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("[ ");
for(Node<Object> node = nodeTop; node != null; node = node.next){
sb.append(node.data.toString() + " ");
}
return sb.toString()+"]";
}
public static void main(String[] args) {
// Stack stack = new Stack(1);
// stack.objPush("abc");
// stack.objPush(123);
// stack.objPush("de");
// stack.objPush("cd");
// stack.objPush("er");
// stack.objPush("hello");
// stack.objPush(666);
// stack.objPush(545);
// stack.objPush("word");
// while (stack.top != -1){
// System.out.println(stack.objPop());
// }
// System.out.println(stack.peekTop());
Stack stack = new Stack();
stack.nodePush("111");
stack.nodePush("222");
stack.nodePush("aaa");
stack.nodePush("bbb");
System.out.println(stack);
while (stack.size > 1)
System.out.println(stack.nodePop());
System.out.println(stack);
}
}
复制代码
O(n)
O(n)
O(1)
O(1)
public class Queue {
/*** * 1.单向队列(Queue):只能在一端插入数据,另外一端删除数据。 * 2.双向队列(Deque):每一端均可以进行插入数据和删除数据操做。 * * 与栈不一样的是,队列中的数据不老是从数组的0下标开始的 * 选择的作法是移动队头和队尾的指针。 * 为了不队列不满却不能插入新的数据,咱们可让队尾指针绕回到数组开始的位置,这也称为“循环队列”。 * */
// 单向循环队列,顺序存储结构实现
private Object[] objQueue;
//队列大小
private int maxSize;
//顶部
private int top;
//底部
private int bottom;
//实际元素
private int item;
public Queue(int size) {
maxSize = size;
objQueue = new Object[maxSize];
top = 0;
bottom = -1;
item = 0;
}
/** * 入队 * @param obj */
public void add(Object obj){
if(item == maxSize){
throw new RuntimeException(obj+" add error, queue is full");
}
//循环队列,首尾结合,下标控制队首和队尾位置
if(bottom == maxSize-1){
bottom = -1;
}
objQueue[++bottom] = obj;
item++;
}
/** * 出对 * @return */
public Object out(){
if(item == 0){
throw new RuntimeException("queue is null");
}
Object obj = objQueue[top];
//声明原顶栈能够回收空间(GC)
objQueue[top] = null;
top++;
//重置下标
if(top == maxSize){
top = 0;
}
item--;
return obj;
}
//链式存储结构实现
private class NodeQueue<Object>{
private Object data;
private NodeQueue next;
public NodeQueue(Object data, NodeQueue next) {
this.data = data;
this.next = next;
}
}
//队列头 出
private NodeQueue queueTop;
//队列尾 进
private NodeQueue queueBottom;
//队列大小
private int size;
public Queue() {
queueTop = null;
queueBottom = null;
size = 0;
}
/** * 入队 * @param obj */
public void addNodeQueue(Object obj){
if(size == 0){
queueTop = new NodeQueue(obj,null);
//指向同一存储地址
queueBottom = queueTop;
}else{
NodeQueue<Object> nodeQueue = new NodeQueue(obj,null);
//让尾节点的next指向新增的节点
queueBottom.next = nodeQueue;
//以新节点做为尾节点
queueBottom = nodeQueue;
}
size ++;
}
/** * 出队 * @return */
public Object removeNodeQueue(){
if(size == 0){
throw new RuntimeException("queue is null");
}
NodeQueue nodeQueue = queueTop;
queueTop = queueTop.next;
//声明原队列头next能够回收空间(GC)
nodeQueue.next = null;
size--;
return nodeQueue.data;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("{ ");
for(NodeQueue nodeQueue = queueTop ; nodeQueue != null ; nodeQueue = nodeQueue.next){
sb.append(nodeQueue.data.toString()+" ");
}
return sb.toString()+"}";
}
public static void main(String[] args) {
Queue queue = new Queue();
queue.addNodeQueue("123");
queue.addNodeQueue("abc");
queue.addNodeQueue("ddd");
System.out.println(queue);
queue.removeNodeQueue();
System.out.println(queue);
queue.removeNodeQueue();
queue.removeNodeQueue();
System.out.println(queue);
}
}
复制代码
单向链表
: 链表中的节点仅指向下一个节点,而且最后一个节点指向空。双向链表
: 其中每一个节点具备两个指针 p、n,使得 p 指向先前节点而且 n 指向下一个节点;最后一个节点的 n 指针指向 null。循环链表
:每一个节点指向下一个节点而且最后一个节点指向第一个节点的链表。O(n)
O(n)
O(1)
O(1)
public class LinkedList {
/*** * 链表一般由一连串节点组成,每一个节点包含任意的实例数据(data fields)和一或两个用来指向上一个/或下一个节点的位置的连接("links") */
private Node head; //链表头
private Node tail; //链表尾
private int size; //节点数
/** * 双端链表 */
public class Node{
private Object data;
private Node prev; //上一个
private Node next; //下一个
public Node(Object data) {
this.data = data;
}
}
public LinkedList() {
this.head = null;
this.tail = null;
this.size = 0;
}
/** * 向链表头添加数据 * @param object */
public void addHead(Object object){
Node node = new Node(object);
if(size == 0){
head = node;
tail = node;
size++;
}else{
head.prev = node;
node.next = head;
head = node;
size++;
}
}
/** * 删除头 */
public void deleteHead(){
//头部指向下一个,prev值为null则说明是链表的头部
if(size != 0){
head.prev = null;
head = head.next;
size--;
}
}
/** *向链表尾添加数据 * @param object */
public void addTail(Object object){
Node node = new Node(object);
if(size == 0){
head = node;
tail = node;
size++;
}else{
node.prev = tail;
tail.next = node;
tail = node;
size++;
}
}
/** * 删除尾部 */
public void deleteTail(){
//尾部指向上一个,next值为null则说明是链表的尾部
if(size != 0){
tail.next = null;
tail = tail.prev;
size--;
}
}
/** * 显示数据 */
public void display(){
Node node = head;
while (size > 0){
System.out.print("["+node.data+"->");
node = node.next;
size--;
}
}
public static void main(String[] args) {
LinkedList linkedList = new LinkedList();
linkedList.addHead("123");
// linkedList.addHead("abc");
// linkedList.addHead("%$$");
// linkedList.addTail("+_+");
// linkedList.addTail("hello");
linkedList.addTail("word");
linkedList.deleteHead();
linkedList.deleteTail();
linkedList.display();
}
}
复制代码
二叉树(由一个根结点和两棵互不相交的、分别称为根结点的左子树和右子树组成) 二叉树便是每一个节点最多包含左子节点与右子节点这两个节点的树形数据结构。
满二叉树
: 树中的每一个节点仅包含 0 或 2 个节点。完美二叉树(Perfect Binary Tree)
: 二叉树中的每一个叶节点都拥有两个子节点,而且具备相同的高度。彻底二叉树
: 除最后一层外,每一层上的结点数均达到最大值;在最后一层上只缺乏右边的若干结点。
O(1)
O(log(n))
O(log(n))
链地址法(Separate Chaining)
: 链地址法中,每一个桶是相互独立的,包含了一系列索引的列表。搜索操做的时间复杂度便是搜索桶的时间(固定时间)与遍历列表的时间之和。开地址法(Open Addressing)
: 在开地址法中,当插入新值时,会判断该值对应的哈希桶是否存在,若是存在则根据某种算法依次选择下一个可能的位置,直到找到一个还没有被占用的地址。所谓开地址法也是指某个元素的位置并不永远由其哈希值决定。无向图(Undirected Graph)
: 无向图具备对称的邻接矩阵,所以若是存在某条从节点 u 到节点 v 的边,反之从 v 到 u 的边也存在。有向图(Directed Graph)
: 有向图的邻接矩阵是非对称的,即若是存在从 u 到 v 的边并不意味着必定存在从 v 到 u 的边。排序
O(nlog(n))
O(n^2)
O(nlog(n))
O(nlog(n))
O(nlog(n))
O(nlog(n))
Ω(n + k)
O(n^2)
Θ(n + k)
Ω(nk)
O(nk)
Θ(nk)
O(|V| + |E|)
O(|V| + |E|)
O(|V| + |E|)
O(|V|^2)
Bellman-Ford 算法
**是在带权图中计算从单一源点出发到其余节点的最短路径的算法。O(|E|)
O(|V||E|)
Floyd-Warshall 算法
可以用于在无环带权图中寻找任意节点的最短路径。O(|V|^3)
O(|V|^3)
O(|V|^3)
Prim 算法
**是用于在带权无向图中计算最小生成树的贪婪算法。换言之,Prim 算法可以在图中抽取出链接全部节点的边的最小代价子集。O(|V|^2)
Kruskal 算法
**一样是计算图的最小生成树的算法,与 Prim 的区别在于并不须要图是连通的。O(|E|log|V|)
s & (1 << k)
s |= (1 << k)
s &= ~(1 << k)
s ^= ~(1 << k)
s << n
s >> n
s & t
s | t
s & ~t
x = x ^ y ^ (y = x)
s & (-s)
~s & (s + 1)
x ^= y; y ^= x; x ^= y;