数据结构从零开始之线性表
线性表
- 线性表在计算机中可以用顺序存储和链式存储两种存储结构来表示。
- 其中用顺序存储结构存储的叫做顺序表。
- 用链式存储结构表示的叫做链表。
顺序存储
- 地址连续
- 预先分配内存,可能会导致浪费
- 查改容易,直接通过下标就可以访问
- 增删不方便,每一次增加或者删除,后面的所有数据元素需要向前移动一位或者向后移动一位
代码实现
public static void main(String[] args)
{
//需要提前分配好空间
int array[]=new int[10];
for (int i = 0; i <array.length ; i++) {
array[i]=i;
}
//查找元素
System.out.println(array[2]);
//修改元素
array[2]=666;
//删除一个元素,后面的所有元素需要向前移动一位
for (int i = 2; i <9 ; i++) {
array[i]=array[i+1];
}
}
链式存储
- 可以动态增长长度
- 增删容易,不用将后面的数据元素进行移动,只需修改指针就行了。
- 查改不方便,需要从头开始遍历进行查找
几种链表
- 单向链表,每一个节点存放一个指针,只指向后一个节点或前一个节点
- 双向链表,每一个节点存放两个指针,一个指向前一个节点,一个指向后一个节点
- 循环链表,尾节点存放一个指针,指向首指针,形成回路
代码实现(单向链表)
public class Demo {
public static void main(String[] args) throws Exception
{
LinkList myList=new LinkList();
for (int i = 0; i < 10; i++) {
myList.insert(i,i);
}
myList.display();
System.out.println(myList.length());
myList.remove(1);
System.out.println(myList.get(1));
System.out.println(myList.indexOf(1));
}
}
class Node
{
public Object data;
public Node next;
public Node(Object data)
{
this.data=data;
}
public Node()
{}
}
class LinkList
{
public Node head;
public LinkList()
{
head=new Node();
}
//清空链表
public void clear()
{
head.next=null;
head.data=null;
}
//判断是否为空
public boolean isEmpty()
{
return head.next==null;
}
//获得链表长度
public int length()
{
Node p=head.next;
int length=0;
while(p!=null)
{
length++;
p=p.next;
}
return length;
}
//按位序号查找
public Object get(int i)throws Exception
{
Node p=head.next;
int j=0;
while(p!=null&&j<i)
{
p=p.next;
j++;
}
if(j>i||p==null)
{
throw new Exception("第"+i+"个元素不存在");
}
return p.data;
}
//按值查找
public int indexOf(Object x)
{
Node p=head.next;
int j=0;
while(p!=null&&!p.data.equals(x))
{
p=p.next;
j++;
}
if(p==null)
return -1;
else
return j;
}
//插入操作
public void insert(int i,Object x)throws Exception
{
Node p=head;
int j=-1;
while(p!=null&&j<i-1)
{
p=p.next;
j++;
}
if(j>i-1||p==null)
{
throw new Exception("第"+i+"个元素不存在");
}
else
{
Node newNode=new Node(x);
newNode.next=p.next;
p.next=newNode;
}
}
//删除操作
public void remove(int i)throws Exception
{
Node p=head;
int j=-1;
while(p.next!=null&&j<i-1)
{
p=p.next;
j++;
}
if(j>i-1||p.next==null)
throw new Exception("删除位置不合法");
p.next=p.next.next;
}
//输出所有节点
public void display()
{
Node node=head.next;
while(node!=null)
{
System.out.println(node.data);
node=node.next;
}
}
}
运行结果
输出所有
0 1 2 3 4 5 6 7 8 9
链表长度为:10
删除第1号元素
输出所有
0 2 3 4 5 6 7 8 9
输出第1号元素
2
获取1的位置
-1
顺序存储vs链式存储
没有哪一种数据结构更好,只有哪一种数据结构更适合某一种场景
- 对于查找较为频繁的宜用顺序表,因为他的查找时间复杂度为O(1)
- 对于增加删除较为频繁的宜用链式表,因为如果用顺序表,插入或者删除的时候后面的数据元素需要全部移动
- 空间上,顺序表需要提前分配好空间,这个时候可能浪费空间也可能空间不够,相对来说,链表更加灵活。
应用(经典的约瑟夫问题)
- 设有编号为1,2,……,n的n(n>0)个人围成一个圈,从第1个人开始报数,报到m时停止报数,杀掉报m的人,再从他的下一个人起重新报数,报到m时停止报数,报m杀掉,……,如此下去,直到最后剩下k个人为止。
用数组来做
import java.util.Scanner;
public class 约瑟夫 {
static int people,deleteNum,aliveNum;
public static void f(int array[],int index,int nowPeople)
{
for(int i=0;i<people;i++)
{
//如果当前人数等于最后要求剩下的人数 跳出循环
if(nowPeople==aliveNum)
return;
//如果不为0表示该人未被杀掉
if(array[i]!=0)
{
//如果数到的数是7
if(index%deleteNum==0)
{
//标识为0 即杀掉
array[i]=0;
//重新报数
index=1;
//现存人数减一
nowPeople--;
}
//如果当前报的数未被杀死 继续报数
else
index++;
}
}
f(array,index,nowPeople);
}
public static void main(String[] args)
{
getData();
kill();
}
/**
* kill
*/
public static void kill()
{
int array[]=new int [people];
for(int n=0;n<people;n++)
{
array[n]=n+1;
}
f(array,1,people);
for(int n:array)
{
if(n!=0)
System.out.println(n);
}
}
/**
* 获取用户输入的数据
*/
public static void getData()
{
Scanner in=new Scanner(System.in);
System.out.println("输入总人数");
people=in.nextInt();
System.out.println("数到第几个人删除");
deleteNum=in.nextInt();
System.out.println("最后剩下几个人");
aliveNum=in.nextInt();
}
}
用双向链表
import java.util.Scanner;
public class 约瑟夫环2 {
static int people,deleteNum,aliveNum;
public static void main(String[] args) {
getData();
Node n=buildLink();
int index=1;
int nowPeople=people;
while(true)
{
//如果当前人数为最后要求剩下的人数 跳出
if(nowPeople==aliveNum)
break;
//如果报到的数为前一位 即如果第七个数的人杀掉 那么报到6的时候,开始杀人
if(index==deleteNum-1)
{
n.next=n.next.next;
n=n.next;
//重新报数
index=1;
//当前人数减一
nowPeople--;
}
//如果不是 继续报数
else
{
n=n.next;
index++;
}
}
print(n);
}
// TODO 自动生成的方法存根
/**
* 获取用户输入的数据
*/
public static void getData()
{
Scanner in=new Scanner(System.in);
System.out.println("输入总人数");
people=in.nextInt();
System.out.println("数到第几个人删除");
deleteNum=in.nextInt();
System.out.println("最后剩下几个人");
aliveNum=in.nextInt();
}
/**
* 创建链表
* @return
*/
public static Node buildLink()
{
Node head=new Node(1);
for (int i = 2; i < people+1; i++) {
head.addNode(new Node(i));
}
head.addNode(head);
return head;
}
/**
* 打印链表
* @param head
*/
public static void print(Node head)
{
Node currentNode=head;
while(true)
{
System.out.print((Integer)currentNode.inner+" ");
currentNode=currentNode.next;
if(currentNode==head)
break;
}
System.out.println();
}
}
两种方式对比
- 经过对比,可以发现,双向链表更容易理解,而且逻辑也更简单,所以说,选择一个正确的数据结构去解决问题是非常重要的。