数据结构
数据结构:研究的是数据如何在计算机中进行组织和存储,使得我们可以高效的获取数据和修改数据。
数据结构可以分为三类:
线性结构: 数组、队列、栈、链表、哈希表…
树型结构:二叉树、二分搜索树、AVL树,红黑树、堆、Trie、线段树、并查集…
图结构:邻接矩阵、邻接表
数据结构 + 算法 = 程序
数组
使用数组时,最重要的就是数组的索引,通过索引可以对数组进行增删改查操作。
数组最大的优点:快速查询。
数组最好应用于索引有语义的情况。
并非所有有语义的数字都可以作为数组的索引,例如:610521199610111188
数组也是可以处理“索引没有语义”的情况
创建数组并对数组进行操作
package com.company.lesson;
public class MyArray<T> {
private T[] data;//数据容器
private int size;//实际存放元素的个数
public MyArray() {
this(10);
}
public MyArray(int capacity) {
data = (T[]) new Object[capacity];
size = 0;
}
//获取数组中存放元素的个数
public int getSize() {
return size;
}
//判断数组是否为空
public boolean isEmpty() {
return size == 0;
}
//向数组末尾添加元素
public void addTail(T ele) throws IllegalAccessException {
add(ele,size);
}
//向数组中指定位置添加元素
public void add(T ele,int index) throws IllegalAccessException {
if(index<0||index>size){
throw new IllegalAccessException("index is error");
}
//判断数组是否已满,满了就进行扩容
if(size == data.length){
resize(2*data.length);
}
//元素后移
for (int i = size-1; i >= index; i--) {
data[i+1] = data[i];
}
data[index] = ele;
size++;
}
//扩容
private void resize(int newcapacity) {
T[] newData = (T[]) new Object[newcapacity];
for (int i = 0; i < size; i++) {
newData[i] = data[i];
}
data = newData;
}
//向数组头部添加元素
public void addHead(T ele) throws IllegalAccessException {
add(ele,0);
}
//获取指定位置元素
public T getEleByIndex(int index) throws IllegalAccessException {
if(index<0||index>size-1){
throw new IllegalAccessException("array is full");
}
return data[index];
}
//修改指定位置元素
public void setEleByIndex(int index,T ele) throws IllegalAccessException {
if(index<0||index>size-1){
throw new IllegalAccessException("array is full");
}
data[index]=ele;
}
//获取头部元素
public T getHead() throws IllegalAccessException {
return getEleByIndex(0);
}
//获取尾部元素
public T getTail() throws IllegalAccessException {
return getEleByIndex(size-1);
}
//判断数组是否包含元素
public boolean contains(T ele){
boolean flag = false;
for (int i = 0; i < size; i++) {
if(data[i]==ele){
flag = true;
break;
}
}
return flag;
}
//搜索指定元素的索引
public int getIndex(T ele){
int index = -1;
// Arrays.stream(data).filter(item->item == ele).findFirst();//filter()过滤
for (int i = 0; i < size; i++) {
if(data[i]==ele){
index = i;
break;
}
}
return index;
}
//移除数组中指定位置元素
public T removeEle(int index) throws IllegalAccessException {
if(index<0||index>size-1){
throw new IllegalAccessException("index is erreor");
}
T result = data[index];
for (int i = index; i < size-1; i++) {
data[i] = data[i+1];
}
size--;
//缩容
if(size <= data.length/4&&data.length/2!=0){
resize(data.length/2);
}
return result;
}
@Override
public String toString() {
StringBuffer sb = new StringBuffer("[");
for (int i = 0; i <size ; i++) {
sb.append(data[i]);
if(i!=size-1){
sb.append(",");//如果不是最后一个元素,就加,
}
}
sb.append("],数组的容量为:");
sb.append(data.length).append(",实际存放元素的个数:").append(size);
return sb.toString();
}
}
复杂度分析
resize的复杂度分析
9次addLast操作,出发resize,总共进行了17次基本操作,平均每次addLast操作,进行两次基本操作。
假设capacity = n,n+1次addLast,chufaresize,总共进行2n+1次基本操作
这样均摊计算:时间复杂度为O(1)
复杂度震荡
栈
栈也是一种线数据结构,只能从栈顶添加或取出元素(后进先出LIFO(Last In First Out))
具体实现
public interface Stack<T> {
/*
基本操作
@param<T>
*/
int getSize();//获取元素数量
boolean isEmpty();//判断是否为空
void push(T ele);//压入栈
T pop();//出栈
T peek();//获得栈顶元素
}
实现该接口的类要重写接口中的方法以及toString()方法
public class MyStack<T> implements Stack<T> {
private MyArray<T> data;//数据容器
private int size;//栈中元素数量
public MyStack() {//构造方法
data = new MyArray<>();
size = 0;
}
@Override
public int getSize() {
return size;
}
@Override
public boolean isEmpty() {
return size == 0;
}
@Override
public void push(T ele) {
try {
data.addTail(ele);
size++;
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
@Override
public T pop() {
T result = null;
try {
result = data.removeTail();
size--;
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return result;
}
@Override
public T peek() {
try {
return data.getTail();
} catch (IllegalAccessException e) {
e.printStackTrace();
return null;
}
}
@Override
public String toString() {
StringBuilder result = new StringBuilder();
result.append("栈中总共有" + size + "个元素");
result.append("栈底[");
try {
for (int i = 0; i < size; i++) {
result.append(data.getEleByIndex(i));
if (i != size - 1) {
result.append(",");
}
}
result.append("]栈顶");
return result.toString();
} catch (IllegalAccessException e) {
e.printStackTrace();
return null;
}
}
}
复杂度分析
队列
队列也是一种线性数据结构
只能从一端(队尾)添加元素,从另一端(队首)取出元素
先进先出(FIFO First In First Out)
具体实现
public interface Queue<T> {
int getSize();//获取元素数量
boolean isEmpty();//判断是否为空
void enqueue(T ele);//入队
T dequeue();//出队
T getFrunt();//查看队首元素
}
实现该接口的类要重写接口中的方法以及toString()方法
public class MyQueue<T> implements Queue<T> {
private MyArray<T> data;
private int size;
public MyQueue() {
data = new MyArray<>();
size = 0;
}
@Override
public int getSize() {
return size;
}
@Override
public boolean isEmpty() {
return size == 0;
}
@Override
public void enqueue(T ele) {
try {
data.addTail(ele);
size++;
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
@Override
public T dequeue() {
T result = null;
try {
result = data.removeHead();
size--;
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return result;
}
@Override
public T getFrunt() {
T result = null;
try {
result = data.getHead();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return result;
}
@Override
public String toString() {
StringBuilder result = new StringBuilder();
result.append("队列中有"+size+"个元素");
result.append("队首[");
try {for (int i = 0; i < size; i++) {
result.append(data.getEleByIndex(i));
if (i != size - 1) {
result.append(",");
}
}
result.append("]队尾");
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return result.toString();
}
}
复杂度分析
dequeue()操作的时间复杂度为O(n),原因时在出队时,数组后面的元素都要进行前移
为了解决前移的问题,可以使用front记录队首位置,使用tail记录入队元素位置,这就是循环队列
1.front == tail 队列为空
3.front == tail 队列为空 (tail +1) % c == front 队列满
循环队列
public class LoopQueue<T> implements Queue<T> {
private T[] data;//数据容量
private int front,tail;//队首位置和待插入元素位置
private int size;
public LoopQueue() {
this(10);
}
public LoopQueue(int capacity){
data = (T[]) new Object[capacity+1];
front = 0;
tail = 0;
}
@Override
public int getSize() {
return size;
}
@Override
public boolean isEmpty() {
return size == 0;
}
@Override
public void enqueue(T ele) {//入队
if((tail+1)%data.length == front){
resize(2*data.length);
}
tail = size;
data[tail] = ele;
size++;
tail = (tail+1)%data.length;
}
private void resize(int newCapacity){//扩容
T[] newdata = (T[]) new Object[newCapacity+1];
for (int i = 0; i < size; i++) {
newdata[i] = data[(front+i)%data.length];
}
tail = size;
front = 0;
data = newdata;
}
@Override
public T dequeue() {//出队
if(front == tail){
return null;
}
T result = data[front];
size--;
front = (front+1)%data.length;
if(size == data.length / 4 && data.length / 2 > 0){//缩容
resize(data.length/2);
}
return null;
}
@Override
public T getFrunt() {
if(front == tail){
return data[front];
}
return null;
}
@Override
public String toString() {
StringBuilder result = new StringBuilder();
result.append("队列中有"+size+"个元素");
result.append("队首[");
int temp = front;
while (front!=tail){
result.append(data[front]+",");
front = (front+1)%data.length;
}
front = temp;
result.append("]队尾");
return result.toString();
}
}
循环队列与数组队列性能比较
public static void compareQueue(Queue queue){
long startTime = System.nanoTime();
int num = 100000;
Random random = new Random();
for (int i = 0; i < num; i++) {
queue.enqueue(random.nextInt(100));
}
while(!queue.isEmpty()){
queue.dequeue();
}
long endTime = System.nanoTime();
System.out.println((endTime-startTime)/1000000000.0);
}
public static void main(String[] args) {
Queue myQueue = new MyQueue();
compareQueue(myQueue);//3.3923022
myQueue = new LoopQueue();
compareQueue(myQueue);//0.0116124
}
循环队列效率更高