2数据结构基础
二数据结构基础
物理结构:顺序存储结构(数组);链式存储结构(链表)
逻辑结构:线性结构(顺序表,栈,队列);非线性结构(树,图)
数组(顺序表):由有限个相同类型的变量所组成的有序集合,在内存中顺序存储
1读取元素
int[] array= new int[]{3,1,2,5,4,9,7,6};
System.out.println(arrray[3]);
2更新元素
int[] array =new int[]{3,1,2,5,4,9,7,6};
array[5]=10;
System.out.println(arrray[5]);
3.1中间插入
public void insert(int element, int index) throws Exception{
if (index<0||index>size){
throw new IndexOutOfBoundsException("超出实际元素范围");
}
for (int i=size-1;i>=index;i--){
array[i+1]=array[index];
}
array[index]=element;
size++;
}
3.2越界插入
public void insert(int element, int index) throws Exception{
if (index<0||index>size){
throw new IndexOutOfBoundsException("超出实际元素范围");
}
if (size>=array.length){
resize();
}
for (int i=size-1;i>=index;i--){
array[i+1]=array[index];
}
array[index]=element;
size++;
}
//数组扩容
public void resize(){
int[] arrayNew=new int[array.length*2];
System.arraycopy(array,0,arrayNew,array.length);
array=arrayNew;
}
4删除元素
public int delete(int index) throws Exception{
if (index<0||index>size){
throw new IndexOutOfBoundsException("超出实际元素范围");
}
int deletedElement=array[index];
for (int i=index;i<size-1;i++){
array[i]=array[i+1];
}
size--;
return deletedElement;
}
数组的插入和删除操作时间复杂度都为O(n)
数组优势:非常高效的随机访问能力,只要给出下标就可以用常量时间找到对应元素。(二分查找)
数组劣势:插入删除会导致大量元素被迫移动,影响效率
总结:数组适合读操作多写操作少的场景
链表:一种在物理上非连续,非顺序的数据结构,由若干节点组成,随机存储
单向链表:包含两部一部分存放数据的变量,另一部分指向下一个节点的指针
private static class Node{
int data;
Node next;
}
双向链表:每一个节点除了拥有data和next指针以外,还有指向前置节点的prev指针
1链表操作的完整代码
private ListNode head;
private ListNode last;
private int size;
//链表插入元素
public void insert(int data,int index) throws Exception{
if (index<0||index>size){
throw new IndexOutOfBoundsException("超出链表节点范围");
}
ListNode insertedNode=new ListNode(data);
if (size==0){
//插入头部
head=insertedNode;
last=insertedNode;
}else if(size==index){
//插入尾部
last.nextNode=insertedNode;
last=insertedNode;
}else {
//插入中间
ListNode prevNode=get(index-1);
ListNode nextNode=prevNode.nextNode;
prevNode.nextNode=insertedNode;
insertedNode.nextNode=nextNode;
}
size++;
}
//链表删除元素
public ListNode remove(int index) throws Exception{
if (index<0||index>=size){
throw new IndexOutOfBoundsException("超出链表节点范围");
}
ListNode removedNode=null;
if (index==0){
//删除头结点
removedNode=head;
head=head.nextNode;
}else if (index==size-1){
//删除尾节点
ListNode prevNode=get(index-1);
removedNode=prevNode.nextNode;
prevNode.nextNode=null;
last=prevNode;
}else{
//删除中间节点
ListNode prevNode=get(index-1);
ListNode nextNode=prevNode.nextNode.nextNode;
removedNode=prevNode.nextNode;
prevNode.nextNode=nextNode;
}
size--;
return removedNode;
}
//链表查找元素
public ListNode get(int index)throws Exception{
if (index<0||index>=size){
throw new IndexOutOfBoundsException("超出链表节点范围");
}
ListNode temp=head;
for (int i=0;i<index;i++){
temp=temp.nextNode;
}
size--;
return temp;
}
//输出链表
public void output(){
ListNode temp=head;
while (temp!=null){
System.out.println(temp.val);
temp=temp.nextNode;
}
}
如果不考虑插入删除操作之前的查找元素过程,只考虑纯粹的插入和删除操作,时间复杂度是O(1)
链表优势:灵活地进行插入和删除操作
劣势:查找元素困难
栈:是一种线性数据结构,它就像放入乒乓球的圆筒容器,栈中元素只能先入后出(FILO),最早进入栈中的元素存放的位置叫作栈底(bottom),最后进入的元素存放的位置叫作栈顶(top)
栈操作:入栈(push)和出栈(pop)
class ArrayStack{
private int maxSize;
private int[] stack;
private int top = -1;
public ArrayStack(int maxSize) {
this.maxSize=maxSize;
stack = new int[this.maxSize];
}
//栈满
public boolean isFull() {
return top == maxSize -1;
}
//栈空
public boolean isEmpty() {
return top == -1;
}
//出栈
public void push(int value) {
if(isFull()) {
System.out.println("栈满");
}
top++;
stack[top] =value;
}
//出栈
public int pop() {
if(isEmpty()) {
throw new RuntimeException("栈空");
}
int value = stack[top];
top--;
return value;
}
//遍历栈
public void list() {
if(isEmpty()) {
System.out.println("栈空,没有数据");
return;
}
for(int i = top;i>=0;i--) {
System.out.printf("stack[%d]=%d\n",i,stack[i]);
}
}
}
入栈和出栈的时间复杂度都为O(1)
队列:是一种线性数据结构,它的特征和行使车辆的单行隧道很相似。队列中元素只能先入先出(FIFO),队列的出口端叫作队头(front),队列的入口端叫作队尾(rear)。
循环队列:当(队尾下标+1)%数组长度=队头下标时,表示队列真的满了,队尾指针指向的位置不存放元素
队列操作:入队(enqueue)出队(dequeue)
循环队列入队和出队的时间复杂度都为O(1)
//循环队列
private int[] array;
private int front;
private int rear;
public MyQueue(int capacity){
this.array=new int[capacity];
}
//入队
public void enQueue(int element) throws Exception{
if ((rear+1)%array.length==front){
throw new Exception("队列已满");
}
array[rear]=element;
rear=(rear+1)%array.length;
}
//出队
public int deQueue()throws Exception{
if (rear==front){
throw new Exception("队列已满");
}
int deQueueElement=array[front];
front=(front+1)%array.length;
return deQueueElement;
}
//输出队列
public void output(){
for (int i=front;i!=rear;i=(i+1)%array.length){
System.out.println(array[i]);
}
}
散列表:(哈希表),这种数据结构提供了键和值的映射关系
哈希函数:在不同语言中,哈希函数实现方式不一样。Java通常用集合HashMap(键值对叫Entry),每一个对象都有属于自己的hashcode,比如index=HashCode(Key)%Array.length
哈希冲突:解决方法1-开方寻址法:当一个key通过哈希函数获得对应的数组下标已被占用时,我们可以寻找下一个空档位置 ;解决方法2-链表法:(被运用在HashMap中):HashMap数组的每一个元素不仅是一个Entry对象,还是一个链表的头节点。每一个Entry对象通过next指针指向它的下一个Entry节点。当新来的Entry映射到与之冲突的数组位置时,只需要插入到对应的链表中即可。
散列表写操作(put):通过哈希函数把key转换为数组下标;把值填到数组小标对应位置。
散列表读操作(get):通过哈希函数把key转化为数组下标;找到数组下标对应的元素,如果是就找到了,如果不是可以顺着链表往下找
散列表扩容(resize):对于JDK中的散列表实现类HashMap来说,影响其扩容的因素有两个:1Capacity(HashMap的当前长度) 2LoadFactor(即HashMap的负载因子,默认值为0.75f)衡量HashMap需要扩容条件如下HashMap.Size>=Capacity*LoadFactor 。而且扩容又有两步:1扩容(创建一个新Entry空数组,长度为原来两倍)2重新Hash(遍历原Entry数组,将所有的Entry重新Hash到新数组中)

浙公网安备 33010602011771号