线性表简介
什么是线性表
在《数据结构简明教程》中如此定义,线性表是由n(n>=0)个相同类型数据元素组成的有限序列。
线性表是一个逻辑结构的概念,
线性表的性质
- a[i-1]是a[i]的前驱元素,a[i+1]是a[i]的后继元素
- 若至少含有一个元素,则只有唯一的开始元素和终端元素,除了开始元素外其他元素有且只有一个前驱元素;除了终端节点外其他元素有且仅有一个后继元素
- 线性表中每个元素有唯一的序号,同一个线性表中可以存在值相同的多个元素,但他们的序号不相同
存储结构
- 顺序表
- 链表
线性表接口
MyList
该接口为整个线性表的抽象,声明了几种常用的方法
使用object类:因为java中所有类都继承object类,所以使用object类可以更加抽象的定义出接口,让其更加适用于所有类型
package com.brozha.datastruct.linearlist;
/**
* @author brozha
* @since 2021/4/12
*/
public interface MyList {
//返回顺序表的长度
public int size();
//判断是否为空
public boolean isEmpty();
//判断是否包含元素e,如果找到就返回下标(从0开始),如果没找到就返回-1
public int indexOf(Object e);
//将数据元素e插入到第i号(i从0开始),将原位置的i后移,成功返回为true,失败返回为false
public boolean insert(Object e,int i);
//删除第i个位置上的元素
public boolean remove(int i);
//获取第i个元素,获取失败则抛出异常
public Object get(int i) throws IndexOutOfBoundsException;
//替换i位置上的值为e,返回失败说明数组越界
public boolean replace(int i,Object e);
}
顺序表
定义
顺序表是线性表采用顺序存储结构在计算机内存中的存储方式,它由多个连续的存储单元构成,每个存储单元存放线性表的一个元素,逻辑上相邻的数据元素在内存中也是相邻的,不需要额外的内存空间来存放元素之间的逻辑关系。顺序表是线性表的直接映射。
也就是说我们可以得出以下公式:
LOC(a[i+1])=LOC(a[i])+K ===> LOC(a[i])=LOC(a[0])+i*K
(其中K为每个元素需要占用K个存储单元,LOC代表该元素的存储地址)
MyArrayList实现
package com.brozha.datastruct.linearlist;
import java.util.ArrayList;
import java.util.Arrays;
/**
* @author brozha
* @since 2021/4/12
*/
public class MyArrayList implements MyList{
private static final int DEFAULT_LEN = 8; //数组默认的大小
private int size; //元素个数
private Object[] elements; //存放元素的数组
public MyArrayList(){
this(DEFAULT_LEN);
}
public MyArrayList(int len){
size = 0;
elements = new Object[len];
}
@Override
public int size() {
return size;
}
@Override
public boolean isEmpty() {
return size>0;
}
/**
*
* @param e
* @return 返回第一次出现的下标,-1表示没找到
*/
@Override
public int indexOf(Object e) {
for(int i = 0;i<size;i++){
if(e.equals(elements[i])){
return i;
}
}
return -1;
}
/**
* 在下标为i的元素之前插入e
* @param e
* @param i 如果i刚好==size,则插入的位置为最后
* @return 如果插入成功则返回true,下标越界则返回false,越界的意思是在size之后
*
*/
@Override
public boolean insert(Object e, int i) {
if(i<0||i>size){
return false;
}
//如果已经满了,则先进行扩容
if(size>=elements.length){
expandSpace();
}
//将i元素以及其之后的元素向后移,再在i处插入e
for(int j=size;j>i;j--){
elements[j] = elements[j-1];
}
elements[i] = e;
size++;
return true;
}
@Override
public boolean remove(int i) {
if(i<0||i>=size){
return false;
}
for(int j = i;j<size-1;j++){
elements[j] = elements[j+1];
}
size--;
return true;
}
@Override
public Object get(int i) throws IndexOutOfBoundsException {
if(i<0||i>=size){
throw new IndexOutOfBoundsException("下标"+i+"越界了");
}
return elements[i];
}
@Override
public boolean replace(int i, Object e) {
if(i<0||i>=size){
return false;
}
elements[i] = e;
return true;
}
//扩容
private void expandSpace(){
//扩容1.5倍
int newLength = elements.length+(elements.length>>1);
//也可以用循环手动扩容
elements = Arrays.copyOf(elements,newLength);
System.out.println("扩容后的总容量"+elements.length);
}
}
思考:深拷贝和浅拷贝?Arrays.copyOf是深拷贝还是浅拷贝?
简单测试(简单的测试了一下,测试可能不完全),若有bug请读者告知
package com.brozha.datastruct.linearlist;
/**
* @author brozha
* @since 2021/4/12
*/
public class ArrayListTest {
public static void main(String[] args) {
MyArrayList mal = new MyArrayList();
System.out.println("初始的长度为:"+mal.size());
for(int i=0;i<15;i++){
mal.insert(i,i);
}
System.out.println("插入15个数之后的值:");
for(int i=0;i<mal.size();i++){
System.out.print(mal.get(i)+" ");
}
System.out.println();
mal.remove(5);
System.out.println("删除下标为5的数后:");
for(int i=0;i<mal.size();i++){
System.out.print(mal.get(i)+" ");
}
System.out.println();
mal.insert(88,5);
System.out.println("在下标为5的数前插入88:");
for(int i=0;i<mal.size();i++){
System.out.print(mal.get(i)+" ");
}
System.out.println();
System.out.println("数字10第一次出现的下标为:"+mal.indexOf(10));
System.out.println("数字12312第一次出现的下表为:"+mal.indexOf(12312));
mal.replace(10,12312);
System.out.println("将下标为10的数替换为12312:");
for(int i=0;i<mal.size();i++){
System.out.print(mal.get(i)+" ");
}
System.out.println();
System.out.println("数字12312第一次出现的下表为:"+mal.indexOf(12312));
}
}
测试结果
链表
定义
线性表的链式存储结构中每个元素的存储空间作为一个节点单独分配,因此逻辑上相邻的元素对应的节点物理上不一定是相邻的,通过增加指针域表示逻辑关系,在插入和删除操作时只需要修改相应的指针域,从而克服了顺序表中插入和删除操作需要移动大量元素的弱点,但同时也失去了顺序表可随机存取的优点。
Node类(单链表)
package com.brozha.datastruct.linearlist;
/**
* @author brozha
* @since 2021/4/14
*/
public class Node {
public Object val;
public Node next;
public Node(){};
public Node(Object val){
this.val = val;
}
}
单链表实现的线性表
package com.brozha.datastruct.linearlist;
/**
* @author brozha
* @since 2021/4/14
*/
public class MyLinkedList implements MyList{
int size;
Node node;
MyLinkedList(){
size = 0;
//相当于是有一个头结点的
node = new Node();
}
@Override
public int size() {
return size;
}
@Override
public boolean isEmpty() {
return size>0;
}
@Override
public int indexOf(Object e) {
Node p = node.next;
int i = 0;
while(p!=null){
if(p.val.equals(e)){
return i;
}
i++;
p = p.next;
}
return -1;
}
//将e插入到第i个位置,后面的往后移动
@Override
public boolean insert(Object e, int i) {
if(i<0||i>size){
return false;
}
Node p = node;
int k = 0;
while(k<i){
k++;
p = p.next;
}
Node q = new Node();
q = p.next;
p.next = new Node(e);
p.next.next = q;
size++;
return true;
}
@Override
public boolean remove(int i) {
if(i<0||i>=size){
return false;
}
Node p = node;
int k = 0;
while(k<i){
k++;
p = p.next;
}
p.next = p.next.next;
size--;
return true;
}
@Override
public Object get(int i) throws IndexOutOfBoundsException {
if(i<0||i>=size){
throw new IndexOutOfBoundsException(i+"越界了!!!");
}
Node p = node.next;
int k = 0;
while(k<i){
k++;
p = p.next;
}
return p.val;
}
@Override
public boolean replace(int i, Object e) {
if(i<0||i>=size){
return false;
}
Node p = node.next;
int k = 0;
while(k<i){
k++;
p = p.next;
}
p.val = e;
return true;
}
@Override
public void add(Object e) {
//头插法
Node p = new Node(e);
p.next = node.next;
node.next = p;
size++;
}
}
测试类
package com.brozha.datastruct.linearlist;
/**
* @author brozha
* @since 2021/4/12
*/
public class ListTest {
public static void main(String[] args) {
MyList mal = new MyLinkedList();
System.out.println("初始的长度为:"+mal.size());
for(int i=0;i<15;i++){
mal.insert(i,i);
}
System.out.println("插入15个数之后的值:");
for(int i=0;i<mal.size();i++){
System.out.print(mal.get(i)+" ");
}
System.out.println();
mal.remove(5);
System.out.println("删除下标为5的数后:");
for(int i=0;i<mal.size();i++){
System.out.print(mal.get(i)+" ");
}
System.out.println();
mal.insert(88,5);
System.out.println("在下标为5的数前插入88:");
for(int i=0;i<mal.size();i++){
System.out.print(mal.get(i)+" ");
}
System.out.println();
System.out.println("数字10第一次出现的下标为:"+mal.indexOf(10));
System.out.println("数字12312第一次出现的下表为:"+mal.indexOf(12312));
mal.replace(10,12312);
System.out.println("将下标为10的数替换为12312:");
for(int i=0;i<mal.size();i++){
System.out.print(mal.get(i)+" ");
}
System.out.println();
System.out.println("数字12312第一次出现的下表为:"+mal.indexOf(12312));
}
}