线性表
头插/尾插/中间插入 头删/尾删
1.顺序表
想细节,写伪代码。
难点:循环边界
2.链表(*****)
结点和结点之间的逻辑关系
一、顺序表
逻辑上有前后关系,物理存储也是前后相连的。
1、插入操作
头插:
思考:(伪代码)
1.需要后移size个元素
2.为避免数据被覆盖,从后往前移
3.空间[size,1] 数据[size-1,0]
4.array[空间的下标]=array[数据的下标]
5.空间的下标=数据的下标+1
插入:(在下标为index的位置上插入item)
思考:(伪代码)
1.需要后移size-index个元素
2.为避免数据被覆盖,从后往前移
3.空间[size,index+1] 数据[size-1,index]
4.array[空间的下标]=array[数据的下标]
5.空间的下标=数据的下标+1
在插入和头插的时候,要注意从后往前插。
尾插:直接插入
2、删除操作
头删:
1.需要移动size-1个数据
2.为避免数据覆盖,从前往后移。
3.空间[0,size-1],数据[1,size]
4.array[空间的下标]=array[数据的下标]
5.空间的下标=数据的下标+1
删除:下标是index的数据
1.需要移动size-index-1个数据
2.为避免数据覆盖,从前往后移。
3.空间[index,size-2],数据[1+index,size-1]
4.array[空间的下标]=array[数据的下标]
5.空间的下标=数据的下标+1
3、时间复杂度
尾插/尾删 O(1) 均摊O(1),在扩容时是O(n)
头插/头删 O(n) 数据搬移是耗时的
中间插入/删除 O(n) 数据搬移是耗时的
查找的时间复杂度 O(1)直接通过下标
4、扩容
假如我们的数组放不下了,扩容
this.array 住的老房子
this.size 房子里住的人
应该搬家 this.size==this.array.length
1)先找新房子
2)搬家
3)this.array=新房子的地址
4)退掉老房子
5、顺序表的代码实现
package com.company.DataStruct.list.arraylist;
public interface IArrayList {
//增 删
/**
* 把item插入到线性表的前面
* @param item 要插入的数据
*/
void pushFront(int item);
/**
* 把item插入到线性表的最后
* @param item
*/
void pushBack(int item);
/**
* 把item插到index下标位置处,index后的数据后移
* @param item
* @param index
*/
void add(int item,int index);
/**
* 删除前面的数据
*/
void popFront();
/**
* 删除最后的数据
*/
void popBack();
/**
* 删除index处的数据,index后的数据前移
* @param index
*/
void remove(int index);
/**
* 查找关键字key是否在链表中
* @param key
* @return 如果在,返回下标;不在,返回-1.
*/
int search(int key);
/**
* 判断关键字key是否在链表中
* @param key
* @return
*/
boolean contains(int key);
/**
* 得到pos位置的值
* @param pos
* @return
*/
int getPos(int pos);
/**
* 打印顺序表
*/
void display();
/**
* 清空顺序表
*/
void clear();
}
package com.company.DataStruct.list.arraylist;
public class MyArrayList implements IArrayList {
private int[] array;//保存数据的空间
private int size;//保存有效数据个数(数据规模)
MyArrayList(int capacity){
this.array=new int[capacity];
this.size=0;
}
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
/**
* 返回下标是index的数据
* @param index
* @return
*/
public int getIndex(int index){
return array[index];
}
@Override
//头插 T(n)=O(n)
public void pushFront(int item) {
ensureCapacity();
//将已有的数据后移一格(从后往前)
for(int i=this.size;i>0;i--){
this.array[i]=this.array[i-1];
}
this.array[0]=item;
this.size++;
}
@Override
public void pushBack(int item) {
ensureCapacity();
if(this.array.length>this.size) {
this.array[size++] = item;
}else{
System.out.println("空间不够,插入失败");
}
}
@Override
public void add(int item, int index) {
ensureCapacity();
// i代表空间的下标
// for(int i=this.size;i>index;i--){
// this.array[i]=this.array[i-1];
// }
if(index<0||index>this.size){
throw new Error();
}
//index=0时是最坏情况,
// i代表数据的下标
for(int i=this.size-1;i>=index;i--){
this.array[i+1]=this.array[i];
}
// i代表循环的次数
// for(int i=0;i<this.size-index;i++){
// this.array[size-i]=this.array[size-i-1];
// }
this.array[index]=item;
this.size++;
}
@Override
public void popFront() {
for(int i=0;i<this.size-1;i++){
this.array[i]=this.array[i+1];
}
this.size--;
}
@Override
public void popBack() {
if(this.size==0){
throw new Error();//抛出异常
}
this.size--;
}
@Override
public void remove(int index) {
if(index<0||index>=this.size){
throw new Error();
}
if(this.size==0){
throw new Error();
}
for(int i=index;i<this.size-1;i++){
this.array[i]=this.array[i+1];
}
this.size--;
}
@Override
public int search(int key) {
for(int i=0;i<this.array.length;i++){
if(key==this.array[i]){
return i;
}
}
return -1;
}
@Override
public boolean contains(int key) {
for(int i=0;i<this.array.length;i++){
if(key==this.array[i]){
return true;
}
}
return false;
}
@Override
public int getPos(int pos) {
if(pos<0||pos>=this.size){
throw new Error();
}
return this.array[pos];
}
@Override
public void display() {
for(int i=0;i<this.size;i++){
System.out.print(this.array[i]+" ");
}
System.out.println();
}
@Override
public void clear() {
for(int i=0;i<this.size;i++){
this.array[i]=0;
}
}
/**
* 扩容,保证数组容量够用 T(n)=O(n)
*/
private void ensureCapacity(){
if(this.size<this.array.length){//当前容量够用来存放新的数据,直接返回。
return;
}
//1.找新房子,找原来的两倍大。
int capacity=this.array.length*2;
int[] newArray=new int[capacity];
//2.搬家
for(int i=0;i<this.size;i++){
newArray[i]=this.array[i];
}
//3.登记新房子的位置,推掉老房子
this.array=newArray;//原来的数组用于没有引用,JDK会自动回收。
}
}
测试代码:
package com.company.DataStruct.list.arraylist;
import com.company.DataStruct.list.arraylist.MyArrayList;
public class Test {
public static void main(String[] args) {
MyArrayList arrayList=new MyArrayList(10);
arrayList.pushBack(1);
arrayList.pushBack(2);
arrayList.pushBack(3);
arrayList.pushFront(6);
arrayList.pushFront(5);//5 6 1 2 3
arrayList.add(4,2);//5 6 4 1 2 3
for(int i=0;i<arrayList.getSize();i++){
System.out.print(arrayList.getIndex(i)+" ");
}
System.out.println();
System.out.println("=======================================");
arrayList.popFront();//6 4 1 2 3
arrayList.remove(2);//6 4 2 3
arrayList.popBack();
System.out.println("=======================================");
for(int i=0;i<arrayList.getSize();i++){
System.out.print(arrayList.getIndex(i)+" ");
}
System.out.println();
System.out.println("=======================================");
System.out.println(arrayList.search(2));//2
System.out.println("=======================================");
System.out.println(arrayList.contains(9));//false
System.out.println(arrayList.getPos(1));//4
//插入10个数
for(int i=0;i<10;i++) {
arrayList.pushBack(i*100);
}
//打印
arrayList.display();
//清空
arrayList.clear();
//打印
arrayList.display();//10个0 清空之后,数组又变为原来的容量
}
}
结果:
二、链表
逻辑上有线性关系 但是物理存储不保证连续(前后相连)。
结点类{值,下一个结点的引用}
链表类 第一个结点的引用
1.链表结点间的关系
引用{value,next},如:
@0xFF{1.@0xCC}
@0xCC{2,@0x33}
@0x33{3,@0x99}
@0x99{4,@0xAA}
@0xAA{5,null}
分析下面的每句代码代表什么意思?
Node p1=//@0xFF
Node p2=//@0xAA
Node p3=//0x@CC
p1.next=p2; <=>@0xFF{1,@0xAA}
p2=p1.next; <=> p2=//@0xCC;
p1.next.next=p2;<=> @0xCC{2,@0xAA}
p1.next.next=p3.next.next;<=> @0xCC{3,@0x99}
p1=p2;<=>p1=@0xAA
2.头删与头插
头插:
1)先创建一个结点
2)设置结点的下一个结点是原来的头结点
3)修改原来链表的头结点为新结点
头删:
1)首先判断链表是否为空
2)如果链表为空,抛出异常
3)如果链表不为空,则修改原来的头结点为原头结点的下一个结点
3.尾删与尾插
尾插:
1)先创建一个结点
2)如果链表为空,则将该结点赋值给链表的头结点。
3)否则获取到链表的最后一个结点,修改最后一个结点的next为新结点。
尾删:
1)先判断链表是否为空。
2)如果为空,则直接抛出异常。
3)如果不为空,寻找链表的倒数第二个结点,修改该结点的next为null。
4)如果链表只有一个结点,则将链表的头结点设置为null。
4、链表的代码实现
package com.company.DataStruct.list.linkedlist;
public class MyLinkedList {
/**
* 链表中的一个结点(内部类)
*/
public class Node{
public int value;//保存的是有效数据
public Node next;//下一个结点的线索(引用)
Node(int v){
this.value=v;
this.next=null;
}
}
// 如果一个结点都没有,head=null
private Node head;//保存链表中第一个结点的引用
public Node getHead() {
return head;
}
MyLinkedList(){
this.head=null;
}
/**
* 头插 将item值的结点插入到链表的最前面
* @param item
*/
public void pushFront(int item){
//先创建一个新结点
Node node=new Node(item);
node.next=head;
this.head=node;
}
/**
* 获取链表的最后一个结点
* @return
*/
private Node getLast(){
Node cur=this.head;
while(cur.next!=null){
cur=cur.next;
}
return cur;
}
/**
* 尾插
* @param item
*/
public void pushBack(int item){
Node node=new Node(item);
if(this.head==null){
this.head=node;
}else{
Node last=getLast();
last.next=node;
}
}
/**
* 头删 T(n)=O(1)
*/
public void popFront(){
if(this.head==null){//链表为空
throw new Error();
}
this.head=this.head.next;
}
/**
* 查找倒数第二个结点
* @return
*/
private Node getSecondLast(){
Node pNode=this.head;
while(pNode.next.next!=null){
pNode=pNode.next;
}
return pNode;
}
/**
* 尾删
*/
public void popBack(){
if(this.head==null){
throw new Error();
}
if(this.head.next==null){//链表中只有一个结点
this.head=null;
}
Node pNode=getSecondLast();
//此时pNode是倒数第二个结点
pNode.next=null;
}
/**
* 如何通过循环,遍历链表的每个结点
*/
void display(){
Node cur=this.head;
while(cur!=null){
System.out.format("%d-->",cur.value);
cur=cur.next;
}
System.out.println("null");
}
}
测试代码:
package com.company.DataStruct.list.linkedlist;
public class TestLinkedList {
public static void main(String[] args) {
MyLinkedList myLinkedList=new MyLinkedList();
myLinkedList.pushFront(1);
myLinkedList.pushFront(2);
myLinkedList.pushBack(3);
myLinkedList.pushBack(4);
myLinkedList.pushBack(5);//2 1 3 4 5
myLinkedList.popBack();
myLinkedList.popFront();//1 3 4
//遍历打印
myLinkedList.display();
}
}
结果: