目录
一、线性表
二、顺序表
1.概念及结构
2.接口实现
我们来实现一个动态顺序表
import java.util.Arrays;
class MyArrayList{
public int[] elem;
public int usedSized;//有效数据长度 不用初始化,因为它默认为0
public MyArrayList(){
this.elem = new int[10];
}
//打印顺序表
public void display(){
for(int i = 0; i < usedSize; i++){
System.out.print(this.elem[i]);
}
System.out.println();
}
//获取顺序表有效数据长度
public int size(){
return this.usedSize;
}
//在pos位置新增元素
public void add(int pos,int data){
if(pos < 0 || pos > this.usedSize){
System.out.println("pos位置不合法!");
return;
}
if(isFull()){
this.elem = Arrays.copyOf(this.elem,2*this.elem.length);
}
for(int i = usedSize - 1; i >= pos; i--){
this.elem[i + 1] = this.elem[i];
}
this.elem[pos] = data;
this.usedSize++;
}
//判断顺序表中元素位置满不满
public boolean isFull(){
return this.usedSize == this.elem.length;
}
// 判定是否包含某个元素
public boolean contains(int toFind){
for(int i = 0; i < this.usedSize; i++){
if(this.elem[i] == toFind){
return true;
}
}
return False;
}
// 查找某个元素对应的位置
public int search(int toFind){
for(int i = 0; i < this.usedSize; i++){
if(this.elem[i] == toFind){
return i;
}
}
return -1;//因为数组下标没有-1
}
// 获取 pos 位置的元素
public int getPos(int pos) {
if(pos < 0 || pos >= this.usedSize){
System.out.println("pos位置不合法!");
return -1;//业务上的处理不考虑
}
return this.elem[pos];
}
// 给 pos 位置的元素设为 value
public void setPos(int pos, int value) {
if(pos < 0 || pos >= this.usedSize){
System.out.println("pos位置不合法!");
return;
}
this.elem[pos] = value;
}
//删除第一次出现的关键字key
public void remove(int toRemove) {
if(isEmpty()){
System.out.println("顺序表为空");
return;
}
int index = search(toRemove);
if(index = -1){
System.out.println("没有你要删除的位置");
return;
}
for(int i = index; i < this.usedSize - 1; i++){
this.elem[i] = this.elem[i+1];
}
usedSize--;
//this.elem[usedSize] = null; 如果数组当中是引用类型
}
//顺序表是否为空
public boolean isEmpty(){
return this.elem.length == 0;
}
//清空顺序表
public void clear(){
this.usedSize = 0;
/* for(int i = 0; i < this.usedSize; i++){
this.elem[i] = null;
}*/ //如果数组当中是引用类型
}
}
public class TestDemo{
public static void main(String[] args){
MyArrayList myArrayList = new MyArrayList();
//这里可以调用方法看看自己的代码是否有问题
}
}
下面我们分析一下部分方法的思路:
从以上代码来看,我们会发现一些有关顺序表的问题:
三、链表
1.概念及结构
2.链表的实现
(1)无头单向非循环链表
//代表一个结点
class ListNode{
public int val;
public ListNode next;//存储的是节点的地址,所以它的类型是节点类型
public ListNode(){
this.val = val;
}
}
public class MyLinkedList{
//穷举法实现一个链表,其实实现一个链表最好用头插或尾插
public MyLinkedList head;
public void createlist(){
ListNode listNode1 = new ListNode(12);
ListNode listNode2 = new ListNode(23);
ListNode listNode3 = new ListNode(34);
ListNode listNode4 = new ListNode(45);
ListNode listNode5 = new ListNode(56);
listNode1.next=listNode2;
listNode2.next=listNode3;
listNode3.next=listNode4;
listNode4.next=listNode5;
//listNode5.next没有赋初值,相当于默认为null
this.head=listNode1;//head指向liatNode1所指向的对象
}
//打印链表 利用head,head去一个位置我们打印一个数,但是为了打印完后仍能找到头,我们要定义一个临时的头
public void display(){
ListNode cur = this.head;
while(cur != null){
System.put.println(cur.val+" ");
cur = cur.next;
}
System.put.println();
}
//查找单链表中是否包含关键字key
public boolean contains(int key){
ListNode cur = this.head;
whilr(cur != null){
if(cur.val == key){
return true;
}
cur = cur.next;
}
return false;
}
//得到单链表的长度
public int size(){
listNode cur = this.head;
int count = 0;
while(cur != null){
count++;
cur = cur.next;
}
return count;
}
}
public static void main(String[] args){
MyLinkedList myLinkedList = new MyLinkedList();
myLinkedList.createlist();
myLinkedList.display();
boolean flg = myLinkedList.contains(56);
System.out.println(flg);
System.out.println(myLinkedList.size());
}
}
编译并运行该代码,输出如下:
12 23 34 45 56
true
5
相信这个穷举法已经帮我们很好的理解好链表的结构,现在我们头插实现一个链表
class ListNode{
public int val;
public ListNode next;//存储的是节点的地址,所以它的类型是节点类型
public ListNode(){
this.val = val;
}
}
public class MyLinkedList{
public MyLinkedList head;
public void display(){
ListNode cur = this.head;
while(cur != null){
System.put.println(cur.val+" ");
cur = cur.next;
}
System.put.println();
}
//头插法
public void addFirst(int data){
listNode node = new listNode(data);//data是一个数据,但是我们要插入的是一个结点,所以我们先new一个结点
/*if(this.head == null){
this.head = node;
}else{
node.next = this.head;
this.head = node;
}*/
//其实是不用分开写的
node.next = this.head;
this.head = node;
}
}
public static void main(String[] args) {
MyLinkedList myLinkedList = new MyLinkedList();
myLinkedList.addFirst(12);
myLinkedList.addFirst(123);
myLinkedList.display();
}
}
编译并运行该代码,输出如下:
123 12
为什么不是12 23?因为它是头插法,23在12的前头插入的
我们还可以用尾插法实现一个链表
class ListNode{
public int val;
public ListNode next;//存储的是节点的地址,所以它的类型是节点类型
public ListNode(){
this.val = val;
}
}
public class MyLinkedList{
public MyLinkedList head;
public void display(){
ListNode cur = this.head;
while(cur != null){
System.put.println(cur.val+" ");
cur = cur.next;
}
System.put.println();
}
//尾插法
public void addLast(int data){
ListNode node = new listNode(data);
ListNode cur = this.head;
if(this.head == null){
this.head = node;
}else{
while(cur.next != null){
cur = cur.next;
}
cur.next = node;
}
}
public static void main(String[] args) {
MyLinkedList myLinkedList = new MyLinkedList();
myLinkedList.addLast(56);
myLinkedList.addLast(123);
myLinkedList.display();
}
}
编译并运行该代码,输出如下:
56 123
这回的顺序是我们想要的了,因为23是在12的后头插的
接下来我们再来看看单链表的其他操作
class ListNode{
public int val;
public ListNode next;//存储的是节点的地址,所以它的类型是节点类型
public ListNode(){
this.val = val;
}
}
public class MyLinkedList{
public MyLinkedList head;
public listNode findIndex(int index){
listNode cur = this.head;
while(index - 1 != 0){
cur = cur.next;
index--;
}
return cur;
}
//任意位置插入,第一个数据节点为0号下标
public void addIndex(int index,int data){
if(index < 0 || index > size()){
return;
}
if(index == 0){
addFirst();
}
if(index == size()){
addLast();
}
listNode node = new listNode(data);
listNode cur = findIndex(index);
node.next = cur.next;
cur.next = node;
}
//删除第一次出现关键字为key的节点
public void remove(int key){
if(this.head == null){
System.out.println("单链表为空,不能删除");
return;
}
if(this.head.next == key){
this.head = this.head.next;
return;
}
ListNode cur = searchPerv(key);
if(cur == null){
System.out.println("没有你要找的节点");
return;
}
ListNode del = cur.next;
cur.next = del.next;
}
public ListNode searchPerv(int key){
ListNode cur = this.head;
while(cur.next != null){
if(cur.next.val == key){
return cur;
}
cur = cur.next;
}
return null;
}
//删除所有值为key的节点
public ListNode removeAllKey(int key){
if(this.head == null){
return null;
}
ListNode prev = this.head;
ListNode cur = this.head.next;
while(cur != null){
if(cur.val == key){
prev.next = cur.next;
cur = cur.next;
}else{
prev = cur;
cur =cur.next;
}
}
if(this.head.val == key){
this.head = this.head.naxt;
}
return this.head;
}
//清除链表
public void clear() {
//法一:(粗暴)
//this.head = null;
//法二(温柔)
while(this.head != null){
ListNode curNext = head.next;
this.head.next = null;
this.head = curNext;
}
}
}
public static void main(String[] args){
MyLinkedList myLinkedList = new MyLinkedList();
myLinkedList.createlist();
myLinkedList.display();
boolean flg = myLinkedList.contains(56);
System.out.println(flg);
System.out.println(myLinkedList.size());
}
}
下面我们分析一下部分方法的思路:
(2)无头双向链表实现
class ListNode{
public int val;
public ListNode prev;
public ListNode next;
public ListNode(int val){
this.val = val;
}
}
public class TestDemo {
public ListNode head;
public ListNode last;
//打印双向链表
public void display(){
ListNode cur = this.head;
while(cur != null){
System.out.print(cur.val+" ");
cur = cur.next;
}
System.out.println();
}
//得到双链表的长度
public int size(){
ListNode cur = this.head;
int count = 0;
while(cur != null){
count++;
cur = cur.next;
}
return count;
}
//查找是否包含关键字key是否在双链表当中
public boolean contains(int key){
ListNode cur = this.head;
while(cur != null){
if(cur.val == key){
return true;
}
cur = cur.next;
}
return false;
}
//头插法
public void addFirst(int data){
ListNode node = new ListNode(data);
if(this.head == null){
this.head = node;
this.last = node;
}else{
node.next = this.head;
this.head.prev = node;
this.head = node;
}
}
//尾插法
public void addLast(int data){
ListNode node = new ListNode(data);
if(this.head == null) {
this.head = node;
this.last = node;
}else{
this.last.next = node;
node.prev = this.last;
this.last = node;
}
}
//任意位置插入,第一个数据节点为0号下标
public void addIndex(int index,int data){
ListNode node = new ListNode(data);
if(index < 0 || index > size()){
return;
}
if(index == 0){
addFirst(data);
return;
}
if(index == size()){
addLast(data);
return;
}
ListNode cur = searchIndex(index);
node.next = cur.prev.next;
cur.prev.next = node;
node.prev = cur.prev;
cur.prev = node;
}
public ListNode searchIndex(int index){
ListNode cur = this.head;
while(index != 0){
cur = cur.next;
index--;
}
return cur;
}
//删除第一次出现关键字为key的节点
public void remove(int key){
ListNode cur = this.head;
while(cur != null){
if(cur.val == key){
//头结点
if(cur == this.head){
this.head = this.head.next;
//如果只有一个结点
if(this.head != null) {
this.head.prev = null;
}else{
last = null;
}
}else{
cur.prev.next = cur.next;
//尾结点
if(cur == this.last){
last = last.next;
}else{
cur.next.prev = cur.prev;//中间结点
}
}
return;
}
cur = cur.next;
}
}
//删除所有值为key的节点
public void removeAllKey(int key){
ListNode cur = this.head;
while(cur != null){
if(cur.val == key){
if(cur == head){
this.head = this.head.next;
if(head != null) {
this.head.prev = null;
}else{
last = null;
}
}else{
cur.prev.next = cur.next;
if(cur == last){
last = last.next;
}else{
cur.next.prev = cur.prev;
}
}
}
cur = cur.next;
}
}
public void clear(){
//法一:
/*this.head = null;
this.last = null;*/
//法二:
while(this.head != null){
ListNode curNext = this.head.next;
this.head.next = null;
this.head.prev = null;
this.head = curNext;
}
this.last = null;
}
public static void main(String[] args) {
TestDemo testDemo = new TestDemo();
//下面我们可以自己调用方法运行一下
}
}
分析部分方法的实现的思路:
四、顺序表和链表的区别与联系
1.顺序表
优势:空间连续、支持随机访问
劣势:①中间或前面部分的插入删除时间复杂度O(N) ②增容的代价比较大
2.链表
优势:①任意位置插入删除时间复杂度为O(1) ②没有增容问题,插入一个开辟一个空间
劣势:以节点为单位存储,不支持随机访问
总结:顺序表和链表的区别
XXX与XXX的不同的问题的作答,记住要从共同点出发,两者的共同点无非就是对数据的组织方式和描述方式不同
组织方式上:
(1)顺序表底层是一个数组,它是一个逻辑上和物理上都是连续的
(2)链表是一个由若干节点组成的一个数据结构,逻辑上是连续的,但是在物理上(内存上)不一定连续
操作上:
(1)顺序表适合查找相关的操作,因为可以使用下标,直接获取某个位置的元素
(2)链表适合于频繁的插入和删除的操作,此时不用像顺序表一样移动元素。链表的插入只需要修改指向
(3)顺序表满了之后要扩容,扩容之后,不一定全能放完,空间利用率不高