## 对几个常见的线性数据结构做一个简单的分析和自定义的实现
1.顺序表
按照顺序存储方式存储的线性表,在计算机的一组连续的存储单元中,因此在查找的时候由于地址连续性,cpu寻址时是按照顺序往下,这样在寻找时会非常迅速,这就导致了顺序存储方式的查找非常高效。同时,由于地址连续性,在中间删除或者增加一个节点时,会影响到后面节点在物理内存中的地址都要往前或往后移动,因此当插入或删除时效率较低,而且随操作影响的节点的数量越多,效率越低。这里用Java实现一个自定义的顺序表
package baseDataStructure;
import java.util.Scanner;
/**
* 顺序表结构
* 即按照顺序存储方式的线性表类似数组
* 每个节点的长度相同,在物理内存中连续存在,一个整的空间存放顺序表
* 便于查询,增删效率低
*/
public class SequentialList {
static final int MAXLEN = 100; //定义顺序表的最大长度
DataList[] listData = new DataList[MAXLEN+1]; //保存顺序表的结构数组
int ListLen; //顺序表已经存的节点的数量
/**
* 初始化为空表,将位置指向0,这样在写入时如果有数据会直接覆盖,而不用先将数据清空
* @param sequentialList
*/
void init(SequentialList sequentialList){
sequentialList.ListLen=0;
}
/**
* 返回顺序表的节点数量
* @param sequentialList
* @return
*/
int getListLength(SequentialList sequentialList){
return sequentialList.ListLen;
}
/**
* 插入节点
* @param sequentialList 顺序表
* @param n 插入的位置
* @param dataList 节点对象
* @return 1表示成功,0表示失败
*/
int insertSequentialList(SequentialList sequentialList,int n,DataList dataList){
int i;
if (sequentialList.ListLen >= MAXLEN){
System.out.println("顺序表已满,无法插入节点");
return 0;
}else if (n<1||n>sequentialList.ListLen-1){
System.out.println("位置有误:当前指定位置没有存储元素,无法插入");
return 0;
}
for (i = sequentialList.ListLen; i >=n ; i--) {
//将插入位置后面的所有元素下标向后移动一位,空出插入位置节点
sequentialList.listData[i+1] = sequentialList.listData[i];
}
sequentialList.listData[n]=dataList;
sequentialList.ListLen++;
return 1;
}
/**
* 在顺序表末尾加上元素
* @param sequentialList
* @param dataList
* @return 1表示成功,0表示失败
*/
int addSequentialList(SequentialList sequentialList,DataList dataList){
if (sequentialList.ListLen>=MAXLEN){
System.out.println("顺序表已满,无法插入节点");
return 0;
}
sequentialList.listData[++sequentialList.ListLen]=dataList;
return 1;
}
/**
* 删除节点位置元素
* @param sequentialList
* @param n
* @return 1表示成功,0表示失败
*/
int delSequentialList(SequentialList sequentialList,int n){
if (n<1||n>sequentialList.ListLen+1){
System.out.println("位置错误:删除节点的位置没有存有元素,无法删除");
return 0;
}
for (int i = n; i<sequentialList.ListLen ; i++) {
//将所有n位置后面的元素都向前挪动一位,然后删除最后一个
sequentialList.listData[i]=sequentialList.listData[i+1];
}
sequentialList.ListLen--;
return 1;
}
/**
* 通过下标位置查找元素
* @param sequentialList
* @param n
* @return
*/
DataList findSequentialList(SequentialList sequentialList,int n){
if (n<1||n>sequentialList.ListLen+1){
System.out.println("位置错误:查找节点的位置没有存有元素,无法查询");
return null;
}else {
return sequentialList.listData[n];
}
}
/**
* 通过关键词(节点类里定义好的key)查询节点的位置返回下标
* @param sequentialList
* @param key
* @return 成功返回下标位置,失败返回0
*/
int findSequentialListByKey(SequentialList sequentialList,String key){
for (int i = 1; i <=sequentialList.ListLen ; i++) {
//compareTo 相等返回0,小于返回-1,大于返回1
if (sequentialList.listData[i].key.compareTo(key)==0){
return i;
}
}
return 0;
}
/**
* 显示当前顺序表所有节点内容
* @param sequentialList
*/
void showAllSequentialList(SequentialList sequentialList){
for (int i = 1; i <=sequentialList.ListLen ; i++) {
System.out.println("第 "+i+" 个元素节点内容是\n"+sequentialList.listData[i].toString());
}
}
/**
* 使用实例
* @param args
*/
public static void main(String[] args) {
SequentialList sequentialList = new SequentialList(); //定义顺序表
DataList dataList; //定义节点引用的变量类型
String key; //定义查询时的关键词
sequentialList.init(sequentialList); //初始化表
Scanner sc = new Scanner(System.in);
//循环添加节点内容
do {
System.out.println("请输入 关键词 姓名 年龄");
DataList tmpData = new DataList();
tmpData.key = sc.next();
tmpData.name = sc.next();
tmpData.age = sc.nextInt();
if (tmpData.age!=0){
if (sequentialList.addSequentialList(sequentialList,tmpData)==0){
break;
}
}else {
break;
}
}while (true);
System.out.println("显示顺序表中的节点顺序");
sequentialList.showAllSequentialList(sequentialList);
System.out.println("要取出的节点号码为:");
int i = sc.nextInt();
dataList = sequentialList.findSequentialList(sequentialList,i);
if (dataList!=null){
System.out.println("第"+i+"个元素内容是"+dataList.toString());
}
System.out.println("输入关键词");
key = sc.next();
i = sequentialList.findSequentialListByKey(sequentialList,key);
dataList = sequentialList.findSequentialList(sequentialList,i);
if (dataList!=null){
System.out.println("关键词为"+key +"的元素内容是"+dataList.toString());
}
}
}
/**
* 定义节点元素内容类,可以是一个任意的实体类
* 类似数组就是一个int 下标和一个Object value的实体类
*/
class DataList{
String key; //节点关键词
String name;
int age;
@Override
public String toString() {
return "DataList{" +
"key='" + key + '\'' +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
2.链表
链表结构是一种动态存储分配的结构形式,一般链表有两个部分,数据部分和地址部分。数据部分用于存储节点内的数据模型等,地址部分则保存下一个节点的地址。在定义链表时,需要定义一个头引用变量(head),这个head指向链表的第一个节点,第一个节点的地址部分指向第二个节点,直到最后一个节点的地址部分指向null 则表示链表完成。因此在物理内存中基本上都是非连续存在的,比如一个8G的内存区,第一个节点在3G的位置,第二个在7G的位置,而CPU需要挨个寻址,会导致查询的效率极低。(一般情况下不会出现,因为系统会给每个进程分配一定的空间 32位系统好像是在4G,前2G作为进程私有空间,后2G作为公用空间,同时在堆空间内,因此一般不会相距太远)但是在增删时只影响上一个节点的地址部分,而不用其他的节点内容,因此效率非常高。
package baseDataStructure;
import java.util.Scanner;
/**
* 链表自定义
* 每个节点包括两个部分,数据部分和地址部分,地址部分指向下一个节点的地址
* 如果是双向链表,地址部分还包含指向上一个节点的地址
* 链表不同于顺序表,在物理内存中不连续
* 便于增删而不便于查找
*/
public class LinkList {
LinkListData nodeData = new LinkListData();
LinkList nextNode;
/**
* 在链表末尾添加节点
* @param linkList 被添加的链表对象
* @param data 需要添加的节点对象
* @return 返回添加完成的节点
*/
LinkList addLinkListAtEnd(LinkList linkList,LinkListData data){
//node 表示需要添加的末尾对象
// headTemp用于存储临时赋值变量
LinkList node,headTemp;
node = new LinkList(); //申请保存节点数据的内存空间
if (node==null){
System.out.println("内存申请失败,节点为空");
return null;
}else {
node.nodeData = data; //将需要添加的数据对象赋值给申请的节点
node.nextNode = null; //将引用节点设置为空,表示这是末尾节点
if (linkList==null){ //如果当前链表还没有存储对象
linkList = node ;
return linkList;
}else {
headTemp = linkList; //将当前链表赋值给headTemp
while (headTemp.nextNode!=null){ //查找链表尾节点
headTemp=headTemp.nextNode;
}
headTemp.nextNode = node;
return linkList;
}
}
}
/**
* 在链表头部添加节点
* @param linkList
* @param data
* @return
*/
LinkList addLinkListAtHead(LinkList linkList,LinkListData data){
LinkList node = new LinkList();
if (node==null){
System.out.println("内存申请失败,节点为空");
return null;
}else {
node.nodeData = data;
node.nextNode = linkList;
linkList = node;
return linkList;
}
}
/**
* 通过关键词查询链表
* @param linkList
* @param key
* @return
*/
LinkList findLinkListByKey(LinkList linkList,String key){
LinkList tmpLinkList = linkList; //保存头节点引用
while (tmpLinkList!=null){
if (tmpLinkList.nodeData.key.compareTo(key)==0){
return tmpLinkList;
}else {
tmpLinkList = tmpLinkList.nextNode;
}
}
return null;
}
/**
* 插入节点,找到关键节点并排其后面
* @param linkList
* @param findKey 插入节点需要找到对应位置,链表查找位置的条件,
* @param data
* @return
*/
LinkList insertLinkList(LinkList linkList,String findKey,LinkListData data){
LinkList node,nodeTemp;
node = new LinkList();
if (node==null){
System.out.println("内存申请失败,节点为空");
return null;
}else {
node.nodeData = data;
nodeTemp = findLinkListByKey(linkList,findKey);
if (nodeTemp!=null){
node.nextNode = nodeTemp.nextNode;
nodeTemp.nextNode = node;
}else {
System.out.println("没有找到插入的位置");
}
return linkList;
}
}
/**
* 删除节点
* @param linkList
* @param findKey
* @return 成功返回1 失败返回0
*/
int delLinkList(LinkList linkList,String findKey){
LinkList node=linkList,nodeTemp=linkList;
while (nodeTemp!=null){
/**
* 具体操作如下
* 假如链表为 1——> 2 ——> 3 ——> 4 需要删除3
* node 第一轮为 1——> 2 ——> 3 ——> 4
* nodeTemp 第一轮为 2 ——> 3 ——> 4
* node 第二轮为 2 ——> 3 ——> 4
* nodeTemp 第二轮为 3 ——> 4
* 第三轮时 nodeTemp 的 头节点关键词为3 执行if 操作
* 此时 node.nextNode ==> 3 ,nodeTemp.nextNode ==> 4
* 将 node.nextNode = nodeTemp.nextNode;
*那么得到的 node 的链表全结果为 1 ——> 2 ——> 3 ——> 4
*
*/
if (nodeTemp.nodeData.key.compareTo(findKey)==0){
node.nextNode = nodeTemp.nextNode;
nodeTemp = null; //释放内存
return 1;
}else {
node = nodeTemp;
nodeTemp=nodeTemp.nextNode;
}
}
return 0;
}
/**
* 得到链表长度
* @param linkList
* @return
*/
int getLengthLinkList(LinkList linkList){
LinkList tmpLinkList = linkList;
int l = 0;
while (tmpLinkList!=null){
l++;
tmpLinkList=tmpLinkList.nextNode;
}
return l;
}
/**
* 显示所有节点内容
* @param linkList
*/
void showAllLinkListNodes(LinkList linkList){
int i= 0;
LinkList tmpLinkList = linkList;
LinkListData data;
while (tmpLinkList!=null){
i++;
data = tmpLinkList.nodeData;
System.out.println("第 "+i+" 个节点内容为 "+data.toString());
tmpLinkList = tmpLinkList.nextNode;
}
}
public static void main(String[] args) {
LinkList linkList ;
LinkList headNode = null;
LinkList LINK = new LinkList();
String key;
Scanner sc = new Scanner(System.in);
System.out.println("输入数据,关键词,姓名,年龄");
do {
LinkListData data = new LinkListData();
data.key = sc.next();
if (data.key.equals("0")){
break;
}else {
data.name = sc.next();
data.age = sc.nextInt();
headNode = LINK.addLinkListAtEnd(headNode,data); //向headNode中添加节点
}
}while (true);
//显示
LINK.showAllLinkListNodes(headNode);
System.out.println("删除节点关键词");
key = sc.next();
LINK.delLinkList(headNode,key);
LINK.showAllLinkListNodes(headNode);
System.out.println("查询关键词");
key = sc.next();
linkList = LINK.findLinkListByKey(headNode,key);
if (linkList!=null){
System.out.println(key+" 查询结果为:"+linkList.nodeData.toString());
}else {
System.out.println(key+" 无此结果");
}
}
}
class LinkListData{
String key;
String name;
int age;
@Override
public String toString() {
return "LinkListData{" +
"key='" + key + '\'' +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
3.栈
从数据的运算分类上看为栈,先进后出。从数据的逻辑结构看是一种线性结构。这里主要说的是顺序栈,即在物理内存中连续存在。链式栈是链式结构存储在内存中的。栈的先进后出机制即先入栈的必须在其他节点弹出后才能取出,在栈中,只能访问当前栈顶的元素,当弹出后才能访问下一个。一般有两个操作,入栈和出栈。
入栈即将数据保存到栈顶,出栈即修改栈顶的引用使其指向下一个元素。
这里可以想象为一个特殊的数组形式,里面存放的是节点保存的数据类型可以是一个封装好的实体类等。而这个数组在添加时按照一定顺序,下标为0的保存栈底的节点,以此类推,当有n个节点保存后,栈顶的指向为n-1。在访问时,首先只能访问第n-1个,访问后将指向改为n-2,以此类推访问至最后一个节点。
package baseDataStructure;
import java.util.Scanner;
/**
* 自定义顺序栈的实现
* 栈是一个先入后出的线性表
* 一般拥有入栈(push) 出栈(pop)两个操作
*/
public class SequenceStack {
static final int MAXLEN = 50;
SequenceStackData[] data = new SequenceStackData[MAXLEN+1];
int top;
/**
* 栈的初始化
* @return
*/
SequenceStack init(){
SequenceStack stack = new SequenceStack();
if (stack!=null){
stack.top = 0; //申请成功后申请栈顶为0
return stack;
}
return null;
}
/**
* 判断是否为空栈,当栈顶top引用为0时,栈为空
* @param stack
* @return 为空返回true
*/
boolean stackIsEmpty(SequenceStack stack){
return stack.top==0;
}
/**
* 判断栈是否已经满了,当栈顶top达到最大值时,栈满
* @param stack
* @return 满了返回true
*/
boolean stackIsFull(SequenceStack stack){
if (stack.top==MAXLEN){
return true;
}else {
return false;
}
}
/**
* 设置栈为空,将top引用置为0
* @param stack
*/
void stackClear(SequenceStack stack){
stack.top = 0;
}
/**
* 释放栈空间
* @param stack
*/
void stackFree(SequenceStack stack){
if (stack!=null){
stack = null;
}
}
/**
* 入栈操作
* @param stack
* @param data
* @return
*/
int stackPush(SequenceStack stack,SequenceStackData data){
if (stack.top>=MAXLEN){
System.out.println("栈已满,无法入栈");
return 0;
}else {
stack.data[++stack.top]=data; //元素入栈
return 1;
}
}
/**
* 出栈操作
* @param stack
* @return
*/
SequenceStackData stackPop(SequenceStack stack){
if (stack.top==0){
System.out.println("栈内没有元素内容");
return null;
}else {
return stack.data[stack.top--];
}
}
/**
* 读取节点数据
* @param stack
* @return
*/
SequenceStackData getStackData(SequenceStack stack){
if (stack.top==0){
System.out.println("没有数据在栈内");
System.exit(0);
}
return stack.data[stack.top];
}
/**
* 测试实例
* @param args
*/
public static void main(String[] args) {
SequenceStack stack = new SequenceStack();
SequenceStackData data = new SequenceStackData();
stack = stack.init(); //初始化
Scanner sc = new Scanner(System.in);
System.out.println("入栈,输入姓名,年龄");
do {
SequenceStackData tmpData = new SequenceStackData();
tmpData.name = sc.next();
if (tmpData.name.equals("0")){
break;
}else {
tmpData.age = sc.nextInt();
stack.stackPush(stack,tmpData);
}
}while (true);
String temp = "1";
System.out.println("出栈:任意非0键进行出栈操作");
temp = sc.next();
while (!temp.equals("0")){
data = stack.stackPop(stack);
System.out.println(data.toString());
temp = sc.next();
}
System.out.println("释放空间");
stack.stackFree(stack);
}
}
/**
* 简单定义栈内节点数据封装类
*/
class SequenceStackData{
String name;
int age;
@Override
public String toString() {
return "SequenceStackData{" +
"name='" + name + '\'' +
", age='" + age + '\'' +
'}';
}
}
4.队列
队列从数据的逻辑结构上讲也是一种线性结构,这里也主要讲顺序队列结构,还有一种链式队列结构。队列是先进先出的规则。一般有两种操作。入队操作,将节点添加到队尾。出队操作:从队头取出节点并删除,使下一个节点成为队头。
package baseDataStructure;
import java.util.Scanner;
/**
* 自定义顺序队列结构
* 先进先出
*
*/
public class SequentialQueue {
static final int QUEUELEN = 15;
QueueData[] data = new QueueData[QUEUELEN];
int head;
int tail; //定义队头和队尾
SequentialQueue queueInit(){
SequentialQueue queue; //申请内存
if ((queue=new SequentialQueue())!=null){
queue.head = 0; //设置队头为0
queue.tail = 0; //设置队尾为0
return queue;
}else {
return null;
}
}
/**
* 判断队列是否为空
* @param queue
* @return 返回0为空
*/
int queueIsEmpty(SequentialQueue queue){
if (queue.head==queue.tail){
return 0;
}else {
return 1;
}
}
/**
* 判断队列是否已满
* @param queue
* @return 返回0为满
*/
int queueIsFull(SequentialQueue queue){
if (queue.tail>=QUEUELEN){
return 0;
}else {
return 1;
}
}
/**
* 清空队列
* @param queue
*/
void queueSetClear(SequentialQueue queue){
queue.head = 0;
queue.tail = 0;
}
/**
* 释放内存
* @param queue
*/
void queueSetFull(SequentialQueue queue){
if (queue!=null){
queue = null;
}
}
/**
* 入队操作
* @param queue
* @param data
* @return 返回1成功 0失败
*/
int queueIn(SequentialQueue queue,QueueData data){
if (queue.tail==QUEUELEN){
System.out.println("队列已满");
return(0);
}else {
queue.data[queue.tail++]=data;
return(1);
}
}
/**
* 出队操作
* @param queue
* @return
*/
QueueData queueOut(SequentialQueue queue){
if (queue.head==queue.tail){
System.out.println("出对操作:队列已空");
System.exit(0);
}else {
return queue.data[queue.head++];
}
return null;
}
/**
* 读取队列数据操作
* @param queue
* @return
*/
QueueData queueGet(SequentialQueue queue){
if (queueIsEmpty(queue)==0){
System.out.println("队列已空");
return null;
}else {
return queue.data[queue.head];
}
}
/**
* 计算队列长度
* @param queue
* @return
*/
int queueLen(SequentialQueue queue){
return (queue.tail-queue.head);
}
/**
* 测试实例
* @param args
*/
public static void main(String[] args) {
SequentialQueue queue = new SequentialQueue();
QueueData data;
Scanner sc = new Scanner(System.in);
queue = queue.queueInit();
System.out.println("进行入队操作:输入姓名,年龄");
do {
QueueData queueData = new QueueData();
queueData.name = sc.next();
queueData.age = sc.nextInt();
if (queueData.name.equals("0")){
break;
}else {
queue.queueIn(queue,queueData);
}
}while (true);
String temp = "1";
System.out.println("出队列操作,输入任何非0键位");
temp = sc.next();
while (!temp.equals("0")){
data = queue.queueOut(queue);
System.out.println("队列内容为 "+data.toString());
temp = sc.next();
}
System.out.println("释放内存");
queue.queueSetFull(queue);
}
}
/**
* 简单队列节点对象数据封装
*/
class QueueData{
String name;
int age;
@Override
public String toString() {
return "QueueData{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}