🎇🎉🎉🎉点进来你就是我的人了
博主主页:🙈🙈🙈戳一戳,欢迎大佬指点!
人生格言: 当你的才华撑不起你的野心的时候,你就应该静下心来学习!
欢迎志同道合的朋友一起加油喔 💪💪💪
目标梦想:进大厂,立志成为一个牛掰的Java程序猿,虽然现在还是一个🐒嘿嘿
谢谢你这么帅气美丽还给我点赞!比个心
文章目录
- 一、ArrayList的问题及思考(前言)
- 1. 插入和删除元素
- 2. 扩容
- 3. 思考
- 二、链表
- 1. 链表的概念及结构
- 2. 结点
- 3. 利用链表存储数据
- 三、链表的分类
- 1. 单向或双向
- 2. 带头或不带头
- 3. 循环或非循环
- 4. 单向带头非循环
- 5. 掌握
- 四、链表的实现
- 1. 如何把一个一个结点 串起来
- 2. 打印链表
- 3. 求当前链表有多少个结点
- 4. 当前链表是否存在 key
- 5. 头插法
- 6. 尾插法
- 7. 在 index 位置 添加数据
- 8. 删除给定的数字 Key
- 9. 删除所有的关键字 Key
- 10. 清空链表
- 五、完整代码
- 1. 异常类
- 2. 接口
- 3. 自己实现的MySingleList类
一、ArrayList的问题及思考(前言)
1. 插入和删除元素
ArrayList
底层使用连续
的空间,任意位置插入或删除元素
时,需要将该位置后序元素整体往前或者往后搬移,故时间复杂度为O(N)
2. 扩容
- 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
- 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继 续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
// ...
// 默认容量是10
private static fi nalint
DEFAULT_CAPACITY =10;
//...
// 数组 :用来存储元素
transient Object[] elementData; // non-private to simplify nested class access
// 有效元素个数
private int size;
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
}
}
// ...
}
3. 思考
如何解决以上问题呢?
随用随取
插入
或者删除元素
是否可以不移动元素Java
集合中又引入了LinkedList
,即链表结构
二、链表
1. 链表的概念及结构
链表
是一种物理存储结构上非连续
存储结构,数据元素的逻辑顺序
是通过链表中的引用链接
次序实现的 。
类似于火车的一节一节车厢
2. 结点
3. 利用链表存储数据
(单向、非循环、不带头链表)
head(头结点)
相当于是一个变量,存放了 0x112
这个地址
是一个引用
,指向第一个结点,通过 头结点 能够找到其他结点
注意:
物理上 (内存)
不一定连续
逻辑上
连续的一种结构
三、链表的分类
1. 单向或双向
2. 带头或不带头
3. 循环或非循环
4. 单向带头非循环
这里的 head 永远表示这个结点,是这个链表的 头结点
5. 掌握
无头单向非循环链表
:结构简单 ,一般不会单独用来存数据。
实际中更多是作为其他数据结构的子结构,如 哈希桶、图的邻接表等等。
另外这种结构在笔试面试中出现很多。
无头双向链表
:在Java的集合框架库中LinkedList底层实现就是无头双向循环链表
。
四、链表的实现
// 1、无头单向非循环链表实现
public class SingleLinkedList {
//头插法
public void addFirst(int data) {
}
//尾插法
public void addLast(int data) {
}
//任意位置插入,第一个数据节点为0号下标
public void addIndex(int index, int data) {
}
//查找是否包含关键字key是否在单链表当中
public boolean contains(int key) {
return false;
}
//删除第一次出现关键字为key的节点
public void remove(int key) {
}
//删除所有值为key的节点
public void removeAllKey(int key) {
}
//得到单链表的长度
public int size() {
return -1;
}
public void clear() {
}
public void display() {
}
}
1. 如何把一个一个结点 串起来
- node1的下一个结点是node2,怎么用代码实现?
node1.next = node2;
- 代码实例
public void createList(){
//在这里面 实例化几个结点
ListNode node1 = new ListNode(12);
ListNode node2 = new ListNode(23);
ListNode node3 = new ListNode(34);
ListNode node4 = new ListNode(45);
node1.next = node2;
node2.next = node3;
node3.next = node4;
this.head = node1;//头结点
}
2. 打印链表
- 怎么从第一个结点走到第二个结点
head = head.next;
- 什么时候算把 结点都遍历完
//判断条件
head != null;
/**
* 打印链表
*/
@Override
public void display() {
ListNode cur = head;
// System.out.println("val值:");
while (cur != null){
System.out.print(head.val + " ");
head = head.next;
}
}
注意点:
定义一个 cur ,让头结点赋值给 cur
这样当链表遍历完时,只是cur为空,头结点还在
3. 求当前链表有多少个结点
/**
* 求当前链表有多少个结点
* @return
*/
@Override
public int size() {
ListNode cur = head;
int count = 0;
while (cur != null){
cur = cur.next;
count++;
}
return count;
}
4. 当前链表是否存在 key
/**
* 当前链表是否存在 key
* @param key
* @return
*/
@Override
public boolean contains(int key) {
ListNode cur = head;
while (cur != null){
if (cur.val == key){
return true;
}
cur = cur.next;
}
return false;
}
5. 头插法
/**
* 头插法
* @param data
*/
@Override
public void addFirst(int data) {
//首先要把 数据存放到一个结点里面
ListNode node = new ListNode(data);
//注意:
//插入数据的时候,一定要先绑后面
//现在 head 指向头结点的地址
node.next = head;
head = node;
//包含链表为空的情况
}
6. 尾插法
/**
* 尾插法
* @param data
*/
@Override
public void addLast(int data) {
//首先要把 数据存放到一个结点里面
ListNode node = new ListNode(data);
//首先,要判断链表是否为空
if (head == null){
head = node;
}else {
//要找到最后一个结点
ListNode cur = head;
while (cur.next != null){
cur = cur.next;
}
cur.next = node;
}
}
7. 在 index 位置 添加数据
要在 index 位置插入数据,就需要把 cur 走到 index 的前一个位置
/**
* 在 index 位置 添加数据
*
* @param index
* @param data
*/
@Override
public void addIndex(int index, int data) throws IndexException {
//还要判断 index 是否合法
if (index < 0 || index > this.size()) {
throw new IndexException("index位置不合法:" + index);
}
ListNode node = new ListNode(data);
if (head == null){
head = node;
return;
}
if (index == 0) {
addFirst(data);
}
if (index == this.size()) {
addLast(data);
}
//处理中间插入
//要找到index 的前一个位置
ListNode cur = searchPrevIndex(index);
node.next = cur.next;
cur.next = node;
}
private ListNode searchPrevIndex(int index){
ListNode cur = head;
//定义一个计数器 进行统计
int count = 0;
while (count != index - 1){
cur = cur.next;
count++;
}
return cur;
}
8. 删除给定的数字 Key
/**
* 删除给定的数字
* 只删除一个 key
* @param key
*/
@Override
public void remove(int key) {
//如果链表等于空,不能删
if (head == null){
return;
}
//要判断头结点
if (head.val == key){
head = head.next;
}
ListNode prevKey = findPrevKey(key);
if (prevKey == null){
// System.out.println("没有要删除的数字");
return;
}
ListNode delNode = prevKey.next;
prevKey.next = delNode.next;
}
private ListNode findPrevKey(int key){
ListNode cur = head;
//首先要找到前驱cur,再删除
while (cur.next != null){
if (cur.next.val == key){
return cur;
}else {
cur = cur.next;
}
}
return null;
}
9. 删除所有的关键字 Key
/**
* 删除所有的关键字 Key
* @param key
*/
@Override
public void removeAllKey(int key) {
//遍历一遍 链表删除所有的 key
//定义两个指针
// prev指向第一个结点 cur指向第二个结点
//从第二个结点向后判断,先不考虑特殊情况
ListNode cur = head.next;
ListNode prev = head;
while (cur != null){
if (cur.val == key){
prev.next = cur.next;
cur = cur.next;
}else {
prev = cur;
cur = cur.next;
}
}
//如果头结点的 val 是 key
if (head.val == key){
head = head.next;
return;
}
}
10. 清空链表
/**
* 清空
*/
@Override
public void clear() {
head = null;
}
五、完整代码
1. 异常类
public class IndexException extends RuntimeException{
public IndexException() {
}
public IndexException(String message) {
super(message);
}
}
2. 接口
public interface IList {
//无头单向非循环链表实现
//头插法
void addFirst(int data);
//尾插法
void addLast(int data);
//任意位置插入,第一个数据节点为0号下标
void addIndex(int index, int data);
//查找是否包含关键字key是否在单链表当中
boolean contains(int key);
//删除第一次出现关键字为key的节点
void remove(int key);
//删除所有值为key的节点
void removeAllKey(int key);
//得到单链表的长度
int size();
void clear();
void display();
}
3. 自己实现的MySingleList类
public class MySingleList implements IList {
//想要创建链表,要知道链表是由 一个一个结点组成的
//先创建结点
//利用静态代码块 内部类
static class ListNode {
//结点由两部分组成
// val 域
public int val;
// 呢 next 域呢?
// next域存放的是 下一个结点的地址
//所以 next 的类型就是结点类型
public ListNode next;
//构造方法
public ListNode(int val) {
this.val = val;
//为什么不构造 next 域呢???
//因为在实例化 结点的时候 不知道next域指向谁
}
}
//链表的属性 链表的头结点
public ListNode head;
public void createList() {
//在这里面 实例化几个结点
ListNode node1 = new ListNode(12);
ListNode node2 = new ListNode(23);
ListNode node3 = new ListNode(34);
ListNode node4 = new ListNode(45);
node1.next = node2;
node2.next = node3;
node3.next = node4;
this.head = node1;//头结点
}
/**
* 打印链表
*/
@Override
public void display() {
ListNode cur = head;
// System.out.println("val值:");
while (cur != null) {
System.out.print(cur.val + " ");
cur = cur.next;
}
}
/**
* 求当前链表有多少个结点
*
* @return
*/
@Override
public int size() {
ListNode cur = head;
int count = 0;
while (cur != null) {
cur = cur.next;
count++;
}
return count;
}
/**
* 当前链表是否存在 key
*
* @param key
* @return
*/
@Override
public boolean contains(int key) {
ListNode cur = head;
while (cur != null) {
if (cur.val == key) {
return true;
}
cur = cur.next;
}
return false;
}
/**
* 头插法
*
* @param data
*/
@Override
public void addFirst(int data) {
//首先要把 数据存放到一个结点里面
ListNode node = new ListNode(data);
//注意:
//插入数据的时候,一定要先绑后面
//现在 head 指向头结点的地址
node.next = head;
head = node;
//包含链表为空的情况
}
/**
* 尾插法
*
* @param data
*/
@Override
public void addLast(int data) {
//首先要把 数据存放到一个结点里面
ListNode node = new ListNode(data);
//首先,要判断链表是否为空
if (head == null) {
head = node;
} else {
//要找到最后一个结点
ListNode cur = head;
while (cur.next != null) {
cur = cur.next;
}
cur.next = node;
}
}
/**
* 在 index 位置 添加数据
*
* @param index
* @param data
*/
@Override
public void addIndex(int index, int data) throws IndexException {
//还要判断 index 是否合法
if (index < 0 || index > this.size()) {
throw new IndexException("index位置不合法:" + index);
}
ListNode node = new ListNode(data);
if (head == null){
head = node;
return;
}
if (index == 0) {
addFirst(data);
}
if (index == this.size()) {
addLast(data);
}
//处理中间插入
//要找到index 的前一个位置
ListNode cur = searchPrevIndex(index);
node.next = cur.next;
cur.next = node;
}
private ListNode searchPrevIndex(int index){
ListNode cur = head;
//定义一个计数器 进行统计
int count = 0;
while (count != index - 1){
cur = cur.next;
count++;
}
return cur;
}
/**
* 删除给定的数字
* 只删除一个 key
* @param key
*/
@Override
public void remove(int key) {
//如果链表等于空,不能删
if (head == null){
return;
}
//要判断头结点
if (head.val == key){
head = head.next;
}
ListNode prevKey = findPrevKey(key);
if (prevKey == null){
// System.out.println("没有要删除的数字");
return;
}
ListNode delNode = prevKey.next;
prevKey.next = delNode.next;
}
private ListNode findPrevKey(int key){
ListNode cur = head;
//首先要找到前驱cur,再删除
while (cur.next != null){
if (cur.next.val == key){
return cur;
}else {
cur = cur.next;
}
}
return null;
}
/**
* 删除所有的关键字 Key
* @param key
*/
@Override
public void removeAllKey(int key) {
//遍历一遍 链表删除所有的 key
//定义两个指针
// prev指向第一个结点 cur指向第二个结点
//从第二个结点向后判断,先不考虑特殊情况
ListNode cur = head.next;
ListNode prev = head;
while (cur != null){
if (cur.val == key){
prev.next = cur.next;
cur = cur.next;
}else {
prev = cur;
cur = cur.next;
}
}
//如果头结点的 val 是 key
if (head.val == key){
head = head.next;
return;
}
}
/**
* 清空
*/
@Override
public void clear() {
head = null;
}
}