一.数据结构概述
数据结构是计算机存储,组织数据的方式
数据结构是指互相之间存在一种或多种特定关系的数据元素的集合
通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率.数据结构往往同高效的检索算法和
索引算法和索引技术有关.
-
作用:
- 模拟生活中的数据
- 作为程序员开发的工具
-
需求:
- 模拟数据存储的案例,模拟上场的球衣号码的存储:
Integer[] players = new Integer[5];
-
作为一个教练,要安排上场:
- 初始容量为5的线性列表,准备用来存储场上的5个球衣号码
- 安排5个球衣上场:[11,22,33,44,55]
- 查询指定位置的球员的球衣号码时多少,查询索引位置为2的球衣号码是33
- 根据球衣号码查询该球员在场上的索引位置,44球衣号的球员在场上的索引为3
- 替换场上索引位置为2的球员,替换之后该位置的球衣编号为333,333把33替换掉了
- 替换球衣号码为22的球员,替换后,为222
- 把场上索引位置为2的球员罚下场(注意:没有补位)
- 按照球员在场上的位置,打印出球衣号码,打印风格[11,22,33,44,55].
二.数组(Array)
新建BasketBallDemo.java
//数据结构数组
public class BasketBallDemo {
private static Integer[] palyer = null;
private static int size = 0;
// 1)初始容量为5的线性列表,准备用来存储场上的5个球衣号码
public static void inti(int initialcapacity) {
palyer = new Integer[initialcapacity];
}
// 2)安排5个球衣上场:[11,22,33,44,55]
public static void add(Integer palyersnum) {
palyer[size] = palyersnum;
size ++;
}
// 3)查询指定位置的球员的球衣号码是多少,查询索引位置为2的球衣号码是33
private static Integer get(int index) {
return palyer[index];
}
// 4)根据球衣号码查询该球员在场上的索引位置,44球衣号的球员在场上的索引为3
private static int getIndex(int num) {
for (int index = 0; index < size; index++) {
if (palyer[index].equals(num)) {
return index;
}
}
return -1;
}
// 5)替换场上索引位置为2的球员,替换之后该位置的球衣编号为333,333把33替换掉了
private static Integer set(int index,Integer num) {
return palyer[index] = num;
}
// 6)替换球衣号码为22的球员,替换后,为222
private static void setNum(Integer begin_num, Integer end_num) {
set(getIndex(begin_num),end_num);
}
// 7)把场上索引位置为2的球员罚下场(注意:没有补位)
private static void delet(int index) {
if (index >= 0 && index <size) {
for (int i = index; i< size - 1; i++) {
palyer[i] = palyer[i + 1];
}
palyer[size -1] = null;
size --;
}else
System.out.println("亲,没有这个球员哦~");
}
// 8)把场上指定球衣号罚下场
private static void deletNum(int num) {
int index = getIndex(num);
delet(index);
}
// 9)按照球员在场上的位置,打印出球衣号码,打印风格[11,22,33,44,55].
public static void print() {
if (palyer == null) {
System.out.println("null");
}
if (size == 0) {
System.out.println("[ ]");
}
StringBuilder sb = new StringBuilder(size * 3 + 1);
sb.append("[");
for (int i = 0; i < size; i++) {
if (i < size - 1) {
sb.append(palyer[i] + ",");
}else{
sb.append(palyer[i]);
}
}
sb.append("]");
System.out.println(sb);
}
public static void main(String[] args) {
// 1)初始容量为5的线性列表,准备用来存储场上的5个球衣号码
inti(5);
// 2)安排5个球衣上场:[11,22,33,44,55]
add(11);
add(22);
add(33);
add(44);
add(55);
// 8)按照球员在场上的位置,打印出球衣号码,打印风格[11,22,33,44,55].
print();
// 3)查询指定位置的球员的球衣号码是多少,查询索引位置为2的球衣号码是33
System.out.println(get(1));
// 4)根据球衣号码查询该球员在场上的索引位置,44球衣号的球员在场上的索引为3
System.out.println(getIndex(33));
// 5)替换场上索引位置为2的球员,替换之后该位置的球衣编号为333,333把33替换掉了
System.out.println(set(2,333));
print();
// 6)替换球衣号码为22的球员,替换后,为222
setNum(22,222);
print();
// 7)把场上索引位置为2的球员罚下场(注意:没有补位)
delet(0);
print();
// 8)把场上指定球衣号的球员罚下场
deletNum(55);
print();
}
}
增加程序的健壮性,数组扩容和数组索引越界
//数据结构数组
public class BasketBallDemo {
private static Integer[] palyer = null;
private static int size = 0;
// 1)初始容量为5的线性列表,准备用来存储场上的5个球衣号码
public static void inti(int initialcapacity) {
palyer = new Integer[initialcapacity];
}
// 2)安排5个球衣上场:[11,22,33,44,55]
public static void add(Integer palyersnum) {
if (size == palyer.length) {
palyer = Arrays.copyOf(palyer, size * 2);
}
palyer[size] = palyersnum;
size ++;
}
// 3)查询指定位置的球员的球衣号码是多少,查询索引位置为2的球衣号码是33
private static Integer get(int index) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("亲,数组索引越界");
}
return palyer[index];
}
// 4)根据球衣号码查询该球员在场上的索引位置,44球衣号的球员在场上的索引为3
private static int getIndex(int num) {
for (int index = 0; index < size; index++) {
if (palyer[index].equals(num)) {
return index;
}
}
return -1;
}
// 5)替换场上索引位置为2的球员,替换之后该位置的球衣编号为333,333把33替换掉了
private static Integer set(int index,Integer num) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("亲,数组索引越界");
}
return palyer[index] = num;
}
// 6)替换球衣号码为22的球员,替换后,为222
private static void setNum(Integer begin_num, Integer end_num) {
set(getIndex(begin_num),end_num);
}
// 7)把场上索引位置为2的球员罚下场(注意:没有补位)
private static void delet(int index) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("亲,数组索引越界");
}
if (index >= 0 && index <size) {
for (int i = index; i< size - 1; i++) {
palyer[i] = palyer[i + 1];
}
palyer[size -1] = null;
size --;
}else
System.out.println("亲,没有这个球员哦~");
}
...
public static void main(String[] args) {
...
}
}
抽取安排球员结构
新建ArrayUtil.java
public class PlayerUtil {
private static Integer[] palyer = null;
private static int size = 0;
// 1)初始容量为5的线性列表,准备用来存储场上的5个球衣号码
public static void inti(int initialcapacity) {
if (index < 0) {
throw new IllegalArgumentException("亲,初始化不能为负数哦");
}
palyer = new Integer[initialcapacity];
}
// 2)安排5个球衣上场:[11,22,33,44,55]
public static void add(Integer palyersnum) {
if (size == palyer.length) {
palyer = Arrays.copyOf(palyer, size * 2);
}
palyer[size] = palyersnum;
size ++;
}
// 3)查询指定位置的球员的球衣号码是多少,查询索引位置为2的球衣号码是33
public static Integer get(int index) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("亲,数组索引越界");
}
return palyer[index];
}
// 4)根据球衣号码查询该球员在场上的索引位置,44球衣号的球员在场上的索引为3
public static int getIndex(int num) {
for (int index = 0; index < size; index++) {
if (palyer[index].equals(num)) {
return index;
}
}
return -1;
}
// 5)替换场上索引位置为2的球员,替换之后该位置的球衣编号为333,333把33替换掉了
public static Integer set(int index,Integer num) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("亲,数组索引越界");
}
return palyer[index] = num;
}
// 6)替换球衣号码为22的球员,替换后,为222
public static void setNum(Integer begin_num, Integer end_num) {
set(getIndex(begin_num),end_num);
}
// 7)把场上索引位置为2的球员罚下场(注意:没有补位)
public static void delet(int index) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("亲,数组索引越界");
}
if (index >= 0 && index <size) {
for (int i = index; i< size - 1; i++) {
palyer[i] = palyer[i + 1];
}
palyer[size -1] = null;
size --;
}else
System.out.println("亲,没有这个球员哦~");
}
// 8)把场上指定球衣号罚下场
public static void deletNum(int num) {
int index = getIndex(num);
delet(index);
}
// 9)按照球员在场上的位置,打印出球衣号码,打印风格[11,22,33,44,55].
public static void print() {
if (palyer == null) {
System.out.println("null");
}
if (size == 0) {
System.out.println("[ ]");
}
StringBuilder sb = new StringBuilder(size * 3 + 1);
sb.append("[");
for (int i = 0; i < size; i++) {
if (i < size - 1) {
sb.append(palyer[i] + ",");
}else{
sb.append(palyer[i]);
}
}
sb.append("]");
System.out.println(sb);
}
}
抽取数据结构(MyArrayList)
public class MyArrayList {
private static Object[] palyer = null;
private static int size = 0;
public MyArrayList(){
this(10);
}
// 1)初始容量
public MyArrayList(int initialcapacity) {
palyer = new Object[initialcapacity];
}
// 2)增加数据,扩容
public static void add(Object palyersnum) {
if (size == palyer.length) {
palyer = Arrays.copyOf(palyer, size * 2);
}
palyer[size] = palyersnum;
size ++;
}
// 3)查询指定索引的数据
public static Object get(int index) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("亲,数组索引越界");
}
return palyer[index];
}
// 4)根据数据查询索引
public static int getIndex(Object num) {
for (int index = 0; index < size; index++) {
if (palyer[index].equals(num)) {
return index;
}
}
return -1;
}
// 5)替换场上索引位置为2的数据
public static Object set(int index,Object num) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("亲,数组索引越界");
}
return palyer[index] = num;
}
// 6)传入替换的数据,和需要替换的数据
public static void setNum(Object begin_num, Object end_num) {
set(getIndex(begin_num),end_num);
}
// 7)根据索引删除数据
public static void delet(int index) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("亲,数组索引越界");
}
if (index >= 0 && index <size) {
for (int i = index; i< size - 1; i++) {
palyer[i] = palyer[i + 1];
}
palyer[size -1] = null;
size --;
}else
System.out.println("亲,数据哦~");
}
// 8)删除指定数据
public static void deletNum(int num) {
int index = getIndex(num);
delet(index);
}
//判空
public boolean isEmtpy() {
if (size == 0) {
return false;
}
return true;
}
// 9)打印数据
public String toString() {
StringBuilder sb = new StringBuilder(size * 3 + 1);
if (isEmtpy()) {
sb.append("[");
for (int i = 0; i < size; i++) {
if (i < size - 1) {
sb.append(palyer[i] + ",");
}else{
sb.append(palyer[i]);
}
}
sb.append("]");
}else
sb.append("数组没有数据");
return sb.toString();
}
//清空数组
public void clean() {
for (int i = 0; i < palyer.length; i++) {
palyer[i] = null;
}
size = 0;
}
public int size() {
return size;
}
}
新建测试类MyArrayUtilDemo
public class MyArrayListDemo {
public static void main(String[] args) {
MyArrayList mal = new MyArrayList();
mal.add("2");
mal.add("d");
mal.add("我");
mal.add("456");
System.out.println(mal.toString());
mal.delet(0);
System.out.println(mal.toString());
System.out.println(mal.get(0));
System.out.println(mal.getIndex("我"));
System.out.println(mal.toString());
mal.clean();
System.out.println(mal.toString());
}
}
三.链表(Linked list)
- 单向链表:只能从头遍历到尾/只能从尾遍历到头
- 双向链表:既可以从头遍历到尾,又可以从尾遍历到头
新建类MyLinkedList
//双向链表
public class MyLinkedList {
private Node first;//链表的第一个节点
private Node last;//链表的最后一个节点
private int size = 0;//节点的数量
//链表中的每一个节点
class Node{
Node prev;//上一个节点对象
Node next;//下一个节点对象
Object ele;
public Node(Object ele){
this.ele = ele;
}
}
//在尾部加入一个节点
public void addLast(Object ele) {
//新建一个节点,保存当前数据
Node node = new Node(ele);
//如果链表长度为0
if (size == 0) {
//把新节点作为第一个节点
this.first = node;
//把新节点作为最后一个节点
this.last = node;
}else {
//把当新节点作为当前链表的最后一个节点的下一个节点
this.last.next = node;
//把当前链表的最后一个节点当做新节点的上一个节点
node.prev = this.last;
//把新增节点作为最后一个节点
this.last = node;
}
//长度+1
size ++;
}
//在头部加入一个节点
public void addFirst(Object ele) {
//新建当前节点,保存当前数据
Node node = new Node(ele);
//假如当前链表的size为0
if (size ==0) {
//把新节点作为第一个节点
this.first = node;
//把新节点作为最后一个节点
this.last = node;
}else {
//把当前链表的第一个节点作为新节点的下一个节点
node.next = this.first;
//把新节点作为链表的第一个节点的上一个节点
this.first.prev = node;
//把新节点作为链表的第一个节点
this.first = node;
}
//长度+1
size ++;
}
//删除一个节点
public void remove(Object ele) {
//找到当前节点
Node current = this.first;
for (int i = 0; i < size -1; i++) {
if (!current.ele.equals(ele)) {
if (current.next == null) {
return;
}
current = current.next;
}
}
//如果当前节点是第一个节点
if (current == this.first) {
//把删除节点的下一个节点作为第一个节点
this.first = current.next;
//把删除节点的下一个节点的prev为空
current.next.prev = null;
}else if(current == this.last) {//如果删除节点是最后一个节点
//把删除节点的上一个节点作为最后一个节点
this.last = current.prev;
//把上一个节点的next的引用为空
current.prev.next =null;
}else {
//如果是中间节点.
//把删除节点的上一个节点作为删除节点的下一个节点的上一个节点
current.next.prev = current.prev;
//把删除节点的下一个节点作为删除节点的上一个节点的的下一个节点
current.prev.next = current.next;
}
//长度-1
size --;
System.out.println(current.ele);
}
//返回该链表的字符串
public String toString() {
if (size == 0) {
System.out.println("[ ]");
}
StringBuilder sb = new StringBuilder(size * 2 + 1);
sb.append("[");
Node current = this.first;
for (int i = 0; i < size; i++) {
sb.append(current.ele);
if (i != size - 1) {
sb.append(",");
}else {
sb.append("]");
}
current = current.next;
}
return sb.toString();
}
}
新建测试类MyLinkedListDemo
public class MyLinkedListDemo {
public static void main(String[] args) {
MyLinkedList mll = new MyLinkedList();
mll.addLast("hk");
mll.addLast("kh");
mll.addLast("uii");
mll.addFirst("ui");
mll.addFirst("first");
System.out.println(mll.toString());
mll.remove("ui");
System.out.println(mll.toString());
}
}
![image][link2]
![image][link3]
四.队列(Queue)
- 单向队列(Queue):先进先出(FIFO),只能从队列尾插入,队列头删除数据
- 双向队列(Deque):可以从队列头或尾添加数据,可以从队列头或尾删除数据
跟链表很像,直接使用MyLinkedList的方法
新建类MyDeque
public class MyDeque extends MyLinkedList{
public void addFirst(Object ele) {
super.addFirst(ele);
}
public void addLast(Object ele) {
super.addLast(ele);
}
public void removeFirst() {
super.remove(this.first.ele);
}
public void removeLast() {
super.remove(this.last.ele);
}
public String queryFirst() {
return first.ele.toString();
}
public String queryLast() {
return last.ele.toString();
}
public static void main(String[] args) {
MyDeque md = new MyDeque();
md.addFirst("a");
md.addFirst("b");
md.addLast("c");
md.addLast("d");
System.out.println(md.toString());
md.removeFirst();
md.removeLast();
System.out.println(md.toString());
System.out.println(md.queryFirst());
System.out.println(md.queryLast());
}
}
五.栈(Stack)
先进后出
![image][stack]
使用数组的方法
新建类MyStack
public class MyStack extends MyArrayList{
//入栈
public void push(Object ele) {
super.add(ele);
}
//出栈
public void pop() {
int index = this.size() - 1;
super.delet(index);
}
//查询栈顶
public Object peek() {
int index = this.size() - 1;
return super.get(index);
}
public static void main(String[] args) {
MyStack s = new MyStack();
s.push("D");
s.push("C");
s.push("B");
s.push("A");
System.out.println(s.toString());
s.pop();
System.out.println(s.toString());
System.out.println(s.peek());
}
}
六.哈希表(Hash)
-
在一般的数组中,元素在数组中的索引位置是随机的,元素的取值和元素的位置之间不存在确定的关系,
因此,在数组中查找特定的值时,需要把查找值和一系列的元素进行比较. -
此时的查找效率依赖于查找过程中所进行的比较次数.
如果元素的值(value)和在数组中的索引位置(index)有一个确定的对应关系(hash)
公式为: index = hash(value);
-
那么对于给定的值,只要调用上述的hash(value)方法,就能找到数组中取值为value的元素的位置.
-
如果数组中元素的值和索引位置存在对应的关系,这样的数组就称之为哈希表,可以看出哈希表最大的
优点就是提供查找数据的效率.
一般情况下,我们是不会吧哈希码(hashCode)作为元素在数组中的索引位置的,因为哈希码很大,数组长度有限,会造
成索引越界问题
这时候,我们可以在哈希码和元素位置之间做某种映射关系. -
元素值 ----hash(value)------>哈希码-------->某种映射关系-------->元素存储索引
注意:每个对象的哈希码是不同的
哈希表的插入和查找是很优秀的
-
可是当哈希表接近装满时,因为数组扩容问题,性能较低(转移到更大的哈希表中)
加载因子:0.75 -
数组中: 会记录添加顺序的,按照索引位置来存储的,允许元素重复.
哈希表中: 元素是不能重复的,对象如果相同则hashCode相同—>index相同
不会记录元素添加的先后顺序