线性表定义
线性表基本运算
增删改查
引用
顺序表定义
顺序表:用顺序存储方式实现的线性表
顺序存储。把逻辑上相邻的元素存储在物理位置上也相邻的存储单元中,元素之间的关系由存储单元的邻接关系来体现。
顺序表java实现
顺序结构就是用数组实现的。
数组的特点:是在内存中顺序存储,因此可以很好地实现逻辑上的顺序表
操作无非是增、删、改、查4种情况。
插入数组元素的操作存在3种情况
- 尾部插入
- 中间插入
- 超范围插入(数组扩容)
中间插入示例:
插入代码结构是:MyArray定义了数组类和相应的插入方法,a类是测试类
package dataStructure.myArray;
public class MyArray {
private int[]array;
private int size;
public MyArray(int capacity){
this.array = new int[capacity];
size=0;
}
/**
* 数组的中间插入(index=size就是尾插)
* @param element 插入的元素
* @param index 插入的位置
*/
public void insert(int element,int index){
if (index<0||index>size){
throw new IndexOutOfBoundsException("插入位置超过实际范围");
}
//先把插入位置之后的元素后挪一位,腾出空间
for(int i=size-1;i>=index;i--){
array[i+1]=array[i];
}
//再把元素放进腾出来的空间中
array[index]=element;
size++;
}
/**
* 输出数组元素的方法
*/
public void output(){
for(int i=0;i<size;i++){
System.out.println(array[i]);
}
}
}
package dataStructure.myArray;
//用于实现小灰的数组代码
public class a {
public static void main(String[] args) {
MyArray myArray = new MyArray(10);
myArray.insert(3,0);
myArray.insert(7,1);
myArray.insert(5,2);
myArray.insert(1,3);
myArray.output();
System.out.println("-----------");
myArray.insert(6,2);
myArray.output();
}
}
代码中的成员变量size是数组实际元素的数量。
如果插入元素在数组尾部,传入的下标参数index等于size;
如果插入元素在数组中间或头部,则index小于size。
如果传入的下标参数index大于size或小于0,则认为是非法输入,会直接抛出异常。
数组扩容
数组的长度在创建时就已经确定了,此时可以创建一个新数组,长度是旧数组的2倍,再把旧数组中的元素统统复制过去,这样就实现了数组的扩容。
插入代码结构是:MyArray2定义了数组类和相应的插入方法,a类是测试类
package dataStructure.myArray;
public class MyArray2 {
private int[]array;
private int size;
public MyArray2(int capacity){
this.array = new int[capacity];
size=0;
}
/**
* 数组的中间插入(index=size就是尾插)
* @param element 插入的元素
* @param index 插入的位置
*/
public void insert(int element,int index){
if (index<0||index>size){
throw new IndexOutOfBoundsException("插入位置超过实际范围");
}
//添加判断条件
//判断实际元素size是否超过数组容量上限度
if(size>=array.length){
resize();
}
//先把插入位置之后的元素后挪一位,腾出空间
for(int i=size-1;i>=index;i--){
array[i+1]=array[i];
}
//再把元素放进腾出来的空间中
array[index]=element;
size++;
}
/**
* 数组扩容方法
*/
public void resize(){
//每次以当前数组长度2背的增长率来扩容(创建新数组)
int[] arrayNew = new int[array.length*2];
//复制过去
System.arraycopy(array,0,arrayNew,0,array.length);
//将新数组的引用给array(array就可以控制新数组了。。。)
array = arrayNew;
//看看扩容后数组的长度是不是两倍关系
System.out.println(array.length);
}
/**
* 输出数组元素的方法
*/
public void output(){
for(int i=0;i<size;i++){
System.out.println(array[i]);
}
}
}
package dataStructure.myArray;
//用于实现小灰的数组代码
public class a {
public static void main(String[] args) {
MyArray2 myArray2 = new MyArray2(3);
myArray2.insert(3,0);
myArray2.insert(7,1);
myArray2.insert(5,2);
myArray2.insert(1,3);
}
}
删除元素
果删除的元素位于数组中间,其后的元素都需要向前挪动1位。
/**
* 删除数组元素
* @param index
* @return
* @throws Exception
*/
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),插入并移动元素的时间复杂度也是O(n),综合起来插入操作的时间复杂度是O(n)。
至于删除操作,只涉及元素的移动,时间复杂度也是O(n)。
数组所适合的是读操作多、写操作少的场景,
链表则恰恰相反
顺序表C++实现
链表定义
链表:用链式存储方式实现的线性表
单向链表
链表(linked list)是一种在物理上非连续、非顺序的数据结构,由若干节点(node)所组成。
单向链表的每一个节点又包含两部分,一部分是存放数据的变量data,另一部分是指向下一个节点的指针next。
对于链表的其中一个节点A,我们只能根据节点A的next指针来找到该节点的下一个节点B,再根据节点B的next指针找到下一个节点C……
双向链表
每一个节点除了拥有data和next指针,还拥有指向前置节点的prev指针。
内存结构图
数组在内存中的存储方式是顺序存储,那么链表在内存中的存储方式则是随机存储。
箭头代表链表节点的next指针
操作还是:增删查改
链表java实现
查
只能从头节点开始向后一个一个节点逐一查找。
链表中的数据只能按顺序进行访问(查找),最坏的时间复杂度是O(n)。
插入
- 尾部插入
- 头部插入
- 中间插入
尾插
头插
中间插
只要内存空间允许,能够插入链表的元素是无穷无尽的,不需要像数组那样考虑扩容的问题。
删除
- 尾部删除
- 头部删除
- 中间删除
尾部删除,是最简单的情况,把倒数第2个节点的next指针指向空即可。
头部删除,也很简单,把链表的头节点设为原先头节点的next指针即可。
中间删除,同样很简单,把要删除节点的前置节点的next指针,指向要删除元素的下一个节点即可。
Java拥有自动化的垃圾回收机制,所以我们不用刻意去释放被删除的节点,只要没有外部引用指向它们,被删除的节点会被自动回收
插入和删除操作,时间复杂度都是O(1)。
为了尾部插入的方便,代码中额外增加了指向链表尾节点的指针last。(如果没有尾指针,就得先查找到尾节点,再操作。。。)
package dataStructure.myLink;
/**
* head,头指针
* last,尾指针
* size,当前链表长度
* Node,一个节点
*/
public class MyLinklist {
private Node head;
private Node last;
private int size;
//静态内部类
//静态内部类可以访问外部类的静态成员,不能访问外部类的普通成员。
/**
* data,数据域
* next,指针域
*/
private static class Node {
int data;
Node next;
//构造方法
Node(int data) {
this.data = data;
}
}
/**
* 链表查找元素
*
* @param index 链表查找元素
* @return
* @throws Exception
*/
public Node get(int index) throws Exception {
if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException("查找超范围!");
}
//从头节点开始查找......
Node temp = head;
for (int i = 0; i < index; i++) {
temp = temp.next;
}
return temp;
}
/**
* 链表插入元素
*
* @param data 插入元素
* @param index 插入位置
* @throws Exception
*/
public void insert(int data, int index) throws Exception {
if (index < 0 || index > size) {
throw new IndexOutOfBoundsException("插入位置超范围!");
}
//理论上,链表不存在扩容问题
//建立节点
Node insertNode = new Node(data);
//判断当前链表状态
if (size == 0) {
//空链表的插入方式
head = insertNode;
last = insertNode;
} else if (index == 0) {
//链表不为空,头插法方式
insertNode.next = head;
head = insertNode;
} else if (index == size) {
//链表不为空,尾插法方式
last.next = insertNode;
last = insertNode;
} else {
//链表不为空,中间插入方式
//先找到插入位置前一个节点
Node preNode = get(index - 1);
insertNode.next = preNode.next;
preNode.next = insertNode;
}
//链表长度加1
size++;
}
/**
* 链表删除元素
*
* @param index 删除的位置
* @return
* @throws Exception
*/
public Node remove(int index) throws Exception {
if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException("删除位置超出链表节点范围");
}
//删除节点的引用
Node removeNode = null;
if (index == 0) {
//删除头节点
removeNode = head;
head = head.next;
} else if (index == size - 1) {
//删除尾节点
//获取尾节点前一个节点
Node prevNode = get(index - 1);
//保存删除的尾节点地址,并将前一个节点指针域置空
removeNode = prevNode.next;
prevNode.next = null;
//移动尾指针
last = prevNode;
} else {
//删除中间节点
Node preNode = get(index - 1);
Node nextNode = preNode.next.next;
removeNode = preNode.next;
//将删除位置前一个节点的指针域指向删除位置后面的节点(完成删除)
preNode.next = nextNode;
}
size--;
return removeNode;
}
/**
* 输出链表
* 从头开始遍历
*/
public void output(){
Node temp = head;
while (temp!=null){
System.out.println(temp.data);
//指针后移
temp = temp.next;
}
}
}