线性表是数据结构中很重要的一部分,是n个具有相同特性的数据元素的有限序列,线性表在逻辑上是线性结构,但其物理结构并不一定连续。其中最常见的线性表:顺序表、链表、栈、队列...。线性表在物理上的存储通常是以数组和链式结构的形式进行存储的。本文主要是对顺序表和单链表的简单操作进行实现。
1、顺序表
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,通常使用数组存储。顺序表可以分为静态顺序表和动态顺序表。静态顺序表是采用定长的数组进行存储,动态顺序表则是使用动态开辟的数组存储。
1.1.1顺序表的定义和构造方法
顺序表的定义主要包含数组和顺序表的有效长度
public int[] elem;//数组
public int usedSize;//顺序表有效长度
public MyArrayList(){
this.elem = new int[10];
}
1.1.2动态顺序表的接口实现
打印顺序表:
public void display() {
for (int i = 0; i <this.usedSize;i++){
System.out.print(this.elem[i]+" ");
}
System.out.println();
}
在给定pos位置新增元素 :在插入元素之前需要判断插入的位置是否合法,以及数组是否满。如果数组已经放满则需要进行扩容操作。
public void add(int pos, int data) {
if((pos < 0) || (pos > this.elem.length) ){
System.out.println("非法插入");
return;
}
if(this.usedSize == this.elem.length){
this.elem = Arrays.copyOf(this.elem,this.elem.length*2);//扩容会产生新的空间,需要将elem指向这块新的空间
}
for (int i = this.usedSize-1; i >= pos ; i--) {
this.elem[i+1] = this.elem[i];
}
this.elem[pos] = data;
this.usedSize++;
}
判断顺序表中是否含有某一个元素:
public boolean contains(int toFind) {
for (int i = 0; i < this.usedSize ; i++) {
if(this.elem[i] == toFind){
return true;
}
}
return false ;
}
查找某个元素的位置,如果不存在返回-1:
public int search(int toFind) {
for (int i = 0; i < this.usedSize ; i++) {
if(this.elem[i] == toFind){
return i;
}
}
return -1 ;
}
获取pos位置上的元素:要先判断pos是否合法以及顺序表是否为空。
public int getPos(int pos) {
if(pos < 0||pos >= this.usedSize){
System.out.println("pos 位置不合法");
return -1;
}
if(this.elem.length == 0){
System.out.println("顺序表为空!");
return -1;
}
return this.elem[pos];
}
删除顺序表中第一次出现关键字key:判断顺序表是否为空,并用search方法判断key是否存在。
public void remove(int toRemove) {
if(this.elem.length == 0){
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];
}
this.usedSize--;
}
2、链表
链表是一种物理存储结构上非连续的存储结构,链表分为单向、双向、带头、不带头、循环、非循环。本文主要涉及无头单向非循环链表以及无头双向链表。
2.1无头单向非循环链表实现
定义节点类:包括节点的值,以及下一个节点的地址next域,构造方法。
class ListNode {
public int val;
public ListNode next;
public ListNode(int val) {
this.val = val;
}
}
定义链表以及链表的操作
定义头结点:
public ListNode 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;
this.head = listNode1;
}
遍历链表:其中cur是引用。
public void display() {
ListNode cur = this.head;
while(cur != null){
System.out.print(cur.val+" ");
cur = cur.next;
}
System.out.println();
}
查找单链表中是否存在关键字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 int size(){
int count = 0;
ListNode cur = this.head;
while(cur != null){
count++;
cur = cur.next;
}
return count;
}
头插法插入节点:
public void addFirst(int data){
ListNode node = new ListNode(data);
node.next = this.head;
this.head = node;
}
尾插法插入节点:需要 先找到尾节点再进行插入节点。
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;
}
}
在给定的index位置插入节点,第一个数据节点为0下标:在单链表中需要注意的是,节点在插入时需要先将插入节点指向原有链表,再将链表指向节点,防止链表的丢失。在插入之前先判断节点插入位置是否合法。需要注意的是findIndex函数返回值的是插入位置的前一个节点,因为单链表不能进行回退操作。
public ListNode findIndex(int index){
ListNode cur = this.head;
while((index-1) != 0){
cur = cur.next;
index--;
}
return cur;
}
public void addIndex(int index,int data) {
if(index < 0 || index > size()){
System.out.println("Index位置不合法!");
return;
}
if(index == 0){
addFirst(data);
return;
}
if(index == size()){
addLast(data);
return;
}
ListNode cur = findIndex(index);
ListNode node = new ListNode(data);
node.next = cur.next;
cur.next = node;
}
删除第一次出现关键字为key的节点:因为单链表不能回退,因此在查找第一次key出现的位置时,应该返回该节点的前一个节点,并且在删除时,需要用del引用记录删除节点的下一个节点的地址。
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;
}
public void remove(int key){
if(this.head == null){
System.out.println("单链表为空,不能删除!");
return;
}
if(this.head.val == 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;
}
删除所有值为key的节点:需要考虑极端情况即全链表或者头结点等于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.next;
}
return this.head;
}
反转一个单链表:curNext用来记录cur.next的地址防止在反转的过程中丢失。
public ListNode reverseList(){
if(this.head == null){
return null;
}
ListNode prev = null;
ListNode cur = this.head;
ListNode curNext = null;
while(cur != null){
curNext = cur.next;
cur.next = prev;
prev = cur;
cur = curNext;
}
return prev;
}
返回链表的中间节点,如果由两个中间节点返回第二个中间节点:需要使用快慢指针的原理,也就是慢指针一次走一步,快指针一次走两步,当快指针到达终点时,满指针到达中点。此种方法不管链表长度是奇数还是偶数都适合。
public ListNode middleNode (){
if(this.head == null){
return null;
}
ListNode fast = this.head;
ListNode slow = this.head;
while(fast != null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
}
return slow;
}
求链表中倒数第k个节点:需要使用前后指针,即前指针先走k-1步,然后前后指针一起往前走。
public ListNode FindKthToTail(int k){
if(k <= 0 || k > size()){
System.out.println("输入的k非法!");
return null;
}
ListNode slow = this.head;
ListNode fast = this.head;
while(k-1 != 0){
fast = fast.next;
k--;
}
while(fast.next != null){
fast = fast.next;
slow = slow.next;
}
return slow;
}
将两个有序表合并成为一个新的有序表:需要创建一个新的节点用来存放新的链表的头结点,值得注意的是跳出while循环后并不清楚是headA或者headB走到表尾,因此需要再进行判断,并将另一个未走完的拼接到新链表后面。
public static ListNode mergeTwoLists(ListNode headA,ListNode headB){
ListNode newHead = new ListNode(-1);
ListNode tmp = newHead;
while(headA != null && headB != null){
if(headA.val < headB.val){
tmp.next = headA;
headA = headA.next;
tmp = tmp.next;
}else {
tmp.next = headB;
headB = headB.next;
tmp = tmp.next;
}
}
if(headA != null){
tmp.next = headA;
}
if(headB != null){
tmp.next = headB;
}
return newHead;
}
给定值x为基准将链表分割为两部分,所有小于x的节点拍在大于她的节点之前:创建两个链表一个用于存放小于x的,一个用于存放大于x的,最后再进行拼接。bs表示小于x的链表的表头,be表示小于x的链表的表尾,as表示大于x的链表的表头,ae表示大于x的链表的表尾。
public ListNode partition(int x) {
ListNode bs = null;
ListNode be = null;
ListNode as = null;
ListNode ae = null;
ListNode cur = this.head;
while(cur != null){
if(cur.val < x){
//1、第一次插入节点
if(bs == null){
bs = cur;
be = cur;
}else {
be.next =cur;
be = be.next;
}
}else {
if(as == null){
as = cur;
ae = cur;
}else {
ae.next = cur;
ae = ae.next;
}
}
cur = cur.next;
}
//预防第一段为空
if(bs == null){
return as;
}
be.next = as;
//预防第二段为空
if(as != null){
ae.next = null;
}
return bs;
}
将有序链表中重合的节点删除,并且不保留重合节点:新建链表用来保存不重复的节点
public ListNode deleteDuplication() {
ListNode cur = head;
ListNode newHead = new ListNode(-1);
ListNode tmp = newHead;
while(cur != null){
if(cur.next != null && cur.val == cur.next.val){
while(cur.next != null && cur.val == cur.next.val) {
cur = cur.next;
}
cur = cur.next;
} else {
tmp.next = cur;
tmp = tmp.next;
cur = cur.next;
}
}
tmp.next = null;
return newHead.next;
}
以上,即为单链表和顺序表的简单实现分享,下一期更新单链表几个习题,以及双链表的一些简单实现,欢迎批评指正!