线性表
首先要了解线性表的基本概念
前驱、后继元素:
假设有这样一线性表【A-B-C-D-E】,则B的前驱元素为A,B的后继元素为C,也可以看出这样一组线性表的A没有前驱元素,E没有后继元素。
得出:
头结点为没有前驱的结点,尾结点尾没有后继的结点
顺序表
数组实现,java api为ArrayList, 容易遍历,这里贴一个根据课程手打的类
package cn.itcast.algorithm.linear;
import java.util.Iterator;
public class SequenceList <T>implements Iterable<T>{
//储存元素的数组
private T[] eles;
//记录当前顺序表中的元素个数
private int N;
//构造方法
public SequenceList(int capacity) {
//初始化数组
this.eles = (T[])new Object[capacity];
//初始化长度
this.N=0;
}
//根据参数newSize,重置eles的大小
public void resize(int newsize){
//定义一个临时数组
T[] temp=eles;
//创建新数组
eles = (T[])new Object[newsize];
//把原数组数据拷贝到新数组
for(int i=0;i<N;i++){
eles[i]=temp[i];
}
}
//将一个线性表置为空表
public void clear(){
this.N=0;
}
//判断当前线性表是否为空表
public boolean isEmpty(){
return N==0;
}
//获取线性表长度
public int length() {
return N;
}
//获取指定位置的元素t
public T get(int i){
return eles[i];
}
//向线性表中添加元素t
public void insert(T t){
if(N== eles.length){
resize(2* eles.length);
}
eles[N++]=t;
}
//在i元素处插入元素t
public void insert(int i,T t){
if(N== eles.length){
resize(2* eles.length);
}
//先将i索引处的元素及其后面的元素依次向后移动移动一位
for(int index=N;index>i;index--){
eles[index]=eles[index-1];
}
//再把t元素放到i索引处即可
eles[i]= t;
//元素个数+1
N++;
}
//删除指定元素i处的位置,并返回该元素
public T remove(int i){
//记录索引处的值
T current = eles[i];
//索引i后面元素依次向前移动一位即可
for(int index=i;index<N-1;index++){
eles[index]=eles[index+1];
}
//元素个数减一
N--;
if (N< eles.length/4){
resize(eles.length/2);
}
return current;
}
//查找t元素第一次出现的位置
public int indexOf(T t){
for(int i=0;i<N;i++){
if(eles[i].equals(t)){
return i;
}
}
return -1;
}
@Override
public Iterator<T> iterator() {
return new SIterator();
}
private class SIterator implements Iterator{
private int cusor;
public SIterator(){
this.cusor = 0;
}
//判断下一个是否还有元素
@Override
public boolean hasNext() {
return cusor<N;
}
@Override
public Object next() {
return eles[cusor++];
}
}
}
如果去读ArrayList的源码,发现和上方法有类似处,只不过api更加广泛、包容。
链表
链表在Java中用内部类实现,实现书中讲的指针,链表容易实现插入操作,链表分为单向链表、双向链表、循环链表。
单向链表
和顺序表的方法类似,也是在类里实现了查找、插入、删除等常用方法
package cn.itcast.algorithm.linear;
import java.util.Iterator;
public class LinkList<T> implements Iterable<T> {
//记录头结点
private Node head;
//记录链表的长度
private int N;
//节点类
private class Node {
//存储数据
T item;
//下一个节点
Node next;
public Node(T item,Node next) {
this.item = item;
this.next = next;
}
}
public LinkList(){
//初始化头结点
this.head = new Node(null,null);
//初始化元素个数
this.N = 0;
}
//清空链表
public void clear(){
head.next=null;
this.N=0;
}
//获取链表长度
public int length(){
return N;
}
//判断链表是否为空
public boolean isEmpty(){
return N==0;
}
//获取指定位置i处的元素
public T get(int i){
//通过循环,从头结点开始往后找,依次找i次,即可
Node n = head.next;
for (int index=0;index<i;index++){
n=n.next;
}
return n.item;
}
//向链表中添加元素t
public void insert(T t){
//找到当前最后一个节点
Node n = head;
while (n.next!=null){
n=n.next;
}
//创建新结点,保存元素t
Node newNode = new Node(t, null);
//当前最后一个结点指向新结点
n.next = newNode;
//元素个数加一
N++;
}
//向指定元素i处增加元素t
public void insert(int i,T t){
//找到i位置前一个结点
Node pre = head;
for(int index=0;index<=i-1;index++){
pre=pre.next;
}
//找到i位置的结点
Node curr = pre.next;
//创建新结点,并将新节点的指向原i节点
Node newNode = new Node(t, curr);
//将i位置前一个节点指向新节点
pre.next=newNode;
//元数个数加一
N++;
}
//删除指定位置i处的元素,并返回被删除的元素
public T remove(int i){
//找到i位置的前一个结点
Node pre = head;
for(int index=0;index<=i-1;i++){
pre=pre.next;
}
//找到i位置处的结点
Node curr = pre.next;
//找到i位置的下一个节点
Node nextNode = curr.next;
//前一个结点指向下一个节点
pre.next=nextNode;
//个数减一
N--;
return curr.item;
}
//查找元素t在链表中第一次出现的位置
public int indexOf(T t){
//从头结点开始,依次找到每一个结点,取出item,和t比较,相同则找到
Node n = head;
for (int i=0;n.next!=null;i++){
n=n.next;
if (n.item.equals(t)){
return i;
}
}
return -1;
}
@Override
public Iterator<T> iterator() {
return new LIterator();
}
private class LIterator implements Iterator{
private Node n;
public LIterator() {
this.n = head;
}
@Override
public boolean hasNext() {
return n.next!=null;
}
@Override
public Object next() {
n=n.next;
return n.item;
}
}
}
双向链表
与单向链表不通,双向链表增加了向前连接,使得可以从前往后亦可以从后往前。
package cn.itcast.algorithm.linear;
import java.util.Iterator;
public class TwoWayLink<T>implements Iterable<T>{
//首结点
private Node head;
//尾结点
private Node last;
//链表长度
private int N;
//结点类
private class Node{
public Node(T item,Node pre,Node next){
this.item = item;
this.pre = pre;
this.next = next;
}
//存储数据
public T item;
//指向上一个结点
public Node pre;
//指向下一个结点
public Node next;
}
public TwoWayLink() {
//初始化头结点
head = new Node(null,null,null);
//初始化长度
this.N=0;
}
//清空链表
public void clear(){
this.head.next=null;
this.last=null;
this.N=0;
}
//获取链表长度
public int length(){
return N;
}
//获取链表是否为空
public boolean isEmpty(){
return N==0;
}
//获取第一个元素
public T getFirst(){
if (isEmpty()){
return null;
}
return head.next.item;
}
//获取最后一个元素
public T getLast(){
if(isEmpty()){
return null;
}
return last.item;
}
//插入元素t
public void insert(T t){
if(isEmpty()){
//如果链表为空
//创建新的结点
Node newNode = new Node(t,head, null);
//让新结点称为尾结点
last=newNode;
//让头结点指向尾结点
head.next=last;
}else {
//如果链表不为空
Node oldlast = last;
//创建新的结点
Node newNode = new Node(t, oldlast, null);
//当前尾结点指向新结点
oldlast.next=newNode;
//让新结点成为尾结点
last = newNode;
}
//元素个数加一
N++;
}
//向i处插入元素
public void insert(int i,T t){
Node n =head;
//找到i位置的前一个结点
for(int index=0;index<=i-1;index++){
n=n.next;
}
//找到i位置的结点
Node curr = n.next;
//创建新结点
Node newNode = new Node(t, n, curr);
//让i位置的前一个结点的下一个结点变为新结点
n.next=newNode;
//让i位置的前一个结点变为新结点
curr.pre=newNode;
//个数+
N++;
}
//获取指定位置i处的元素
public T get(int i){
Node n = head.next;
for (int index=0;index<i;index++){
n=n.next;
}
return n.item;
}
//找到元素t第一次出现的位置
public int indexOf(T t){
Node n =head;
for (int index=0;n.next!=null;index++){
n=n.next;
if (n.next.equals(t)){
return index;
}
}
return -1;
}
//删除i位置处的元素,并返回该元素
public T remove(int i) {
Node n =head;
//找到i位置的前一个结点
for(int index=0;index<=i-1;index++){
n=n.next;
}
//找到i位置处的结点
Node curr = n.next;
//找到i位置处的下一个结点
Node oldlast = curr.next;
//i位置的上一个结点的下一个结点变为i位置的下一个结点
n.next=oldlast;
//i位置的下一个结点的上一个结点变为i位置的前一个结点
oldlast.pre=n;
//个数减一
N--;
return curr.item;
}
@Override
public Iterator<T> iterator() {
return new TIterator();
}
private class TIterator implements Iterator<T>{
private Node n;
public TIterator() {
this.n=head;
}
@Override
public boolean hasNext() {
return n.next!=null;
}
@Override
public T next() {
n=n.next;
return n.item;
}
}
}
Java自带的api,LinkedList,就是用双向链表实现的。
单向链表反转
递归进行反转,课程手打代码记录
public void reverse(){
//判断当前列表是否为空列表,不是则调用重载的reverse完成反转
if (isEmpty()){
return;
}
reverse(head.next);
}
//返转指定的结点curr,并把反转后的结点返回
public Node reverse(Node curr){
if(curr.next==null){
head.next=curr;
return curr;
}
//递归的反转当前结点curr的下一个结点;返回值就是链表反转后,当前结点的上一个结点
Node pre = reverse(curr.next);
//把返回的结点的下一个结点变成当前结点curr;
pre.next=curr;
//把当前结点的下一个结点变为null
curr.next=null;
return curr;
}
有head>1>2>3>4链表,四次递归:
四次:null<4<head
三次:null<3<4<head
两次:null<2<3<4<head
一次:null<1<2<3<4<head
判断链表是否有环
定义快慢指针,快指针一次两步、慢指针一次一步,两指针如果相遇,即存在环。当两者相遇,定义新指针在链表起点,步长与慢指针一样,当慢指针与新指针再次相遇,相遇点即为环的入口。
循环链表解决约瑟夫问题
约瑟夫问题:在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。然而Josephus 和他的朋友并不想遵从。Josephus要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。
public class JosephTest {
public static void main(String[] args) {
//解决约瑟夫问题
//建立循环链表,包含41个结点,储存1-41的结点
//首结点
Node<Integer> first=null;
//记录前一个结点
Node<Integer> pre=null;
for(int i =1;i<=41;i++){
//如果是第一个结点
if(i==1){
first = new Node<>(1,null);
pre = first;
continue;
}
//如果不是第一个结点
Node<Integer> newNode = new Node<>(i, null);
pre.next=newNode;
pre = newNode;
//如果是最后一个结点,那么需要让最后一个结点的下一个结点变为first
if(i==41){
pre.next = first;
}
}
//count计数器,模拟报数
int count =0;
//遍历循环链表
//记录每次遍历拿到的结点,默认从首结点开始
Node<Integer> n = first;
//记录当前结点的上一个结点、
Node before = null;
while(n!=n.next){
//模拟报数
count++;
//判断当前报数是否为3
if(count==3){
//如果是3,则把当前结点删除,打印该结点,重置count为0,让当前结点n后移动
before.next = n.next;
System.out.print(n.item+",");
count=0;
n=n.next;
}else{
//不为3,让before变为当前结点,让当前结点后移
before=n;
n=n.next;
}
}
//打印最后一个元素
System.out.print(n.item);
}
//节点类
private static class Node<T> {
//存储数据
T item;
//下一个节点
Node next;
public Node(T item, Node next) {
this.item = item;
this.next = next;
}
}
}
输出结果末两位分别为16、31。