1.常用的数据结构
- 1.数组(Array)
数组可以说是最基本的数据结构。它是将相同类型的一些数据有序的集合在一起。
一个数组可以包含多个同类型的数据元素。
可以按照数据类型分为整型数组、字符型数组、浮点型数组、对象数组等。
可以按照维度分为一维数组、二维数组、多维数组等。 - 2.队列(Queue)
队列也是一种特殊的线性表。只允许在一端(队尾)进行插入,在另一端(队头)进行删除。
线性表就是各个数据元素之间具有线性关系。就像一条线一样。有头有尾,中间一个结点连着一个结点,不可间断。
队列就像水管一样,水只能从一端流入,从另一端流出。 - 3.链表(Linked List)
链表的元素是按照链式存储的。也是线性结构中的一种。线性结构包括顺序结构和链表结构两种。
这种存储在物理上并非连续的,而是每个元素包括两个部分,一部分是自己的数据,另一部分是引用域,指向下一个元素所在的地址。
链表的逻辑关系是通过引用域串联起来的。
线性结构中的顺序表结构在进行插入或者删除的时候,需要移动大量的数据。而链表结构最多只需要修改关联的前后两个结点的引用域即可。
链表包含单向链表和双向链表,单向链的引用域只指向下一个元素的地址,双向链表的引用域既包含指向下一个的地址,也有指向上一个的地址。 - 4.树(Tree)
典型的非线性结构,有且仅有一个根结点,根结点会有一个或多个子结点。
然后子结点也可能会有一个或多个子结点。但是,每个子结点都只会有一个父结点。
终结点没有子结点。
每个节点后面的所有节点组成的树称为这个结点的子树。
二叉树是一种比较特殊的树,每一个结点最多都只有两个子结点。称为左结点和右结点。
二叉树又包括完全二叉树(有子结点的结点都有两个子结点),和非完全二叉树(有些结点只有一个子结点)。 - 5.堆(Heap)
堆是一种特殊的树形结构,我们一般讨论的堆都是二叉堆。
堆的特点是根结点的值是所有结点中最小的或者最大的。并且根结点的子树也是一个堆结构。 - 6.栈(Stack)
栈是一种特殊的线性表,只能在表的一端(栈顶)进行插入(入栈)和删除(出栈)操作。
第一个插入的数据会被后面插入的不断的压入栈底。要想操作栈底的数据,只能先将其推到栈顶。 - 7.图(Graph)
图是另外一种非线性结构,例如通信网络、交通网络、人机关系网络,都可以归为图结构
图可以分为无向图和有向图,
无向图没有方向,从A到B和从B到A一样,
有向图具有方向,就像单行道,只能从A到B,不可以逆向从B到A
连接图的某个定点到其他所有定点的边的数量称为该顶点的度。 - 8.散列表(Hash)
源自散列函数(Hash function),其思想是如果在结构中存在关键字和T相等的记录,那么必定在F(T)的存储位置可以找到该记录,这样就可以不用进行比较而直接取得所查记录
2.链表结构

/*** 定义数据结构中的元素*/static class DataElement {String key;String other_messages;public DataElement(String key, String other_messages) {this.key = key;this.other_messages = other_messages;}}/*** 定义链表结构* 链表包含单向链表和双向链表,单向链的引用域只指向下一个元素的地址,双向链表的引用域既有指向下一个的,也有指向上一个的*/static class DataLinkedList {DataElement dataElement;DataLinkedList nextNode;//我们这里只做查找算法的示例,省去添加节点,删除节点等其他方法,只做查找/*** 在链表的尾部追加数据** @param head 链表的表头* @param data 要添加的数据* @return 添加后的链表元素(数据+引用域)*/static DataLinkedList addData(DataLinkedList head, DataElement data) {DataLinkedList node = new DataLinkedList();//新追加的结点是在结尾追加node.dataElement = data;//保存数据node.nextNode = null;//表尾引用域为null,DataLinkedList element = head;if (element == null) {//链表的表头为null,是个空链表,直接将head指向新节点head = node;return head;}while (element.nextNode != null) {//找到链表的尾部,如果已知链表尾部,则可以直接用,不用再从表头查找element = element.nextNode;}element.nextNode = node;//将链表的尾部指向新追加的结点return node;}/*** 在表头追加数据** @param head 表头* @param data 要添加的数据* @return 添加后的链表元素(数据+引用域)*/static DataLinkedList addHeadData(DataLinkedList head, DataElement data) {DataLinkedList node = new DataLinkedList();//新追加的结点是在链表头部追加node.dataElement = data;//保存数据node.nextNode = head;//指向下一个元素是之前的表头head = node;//表头变成新增的这个元素return head;}/*** 在指定的key后面追加数据** @param head 表头* @param key 元素数据的key* @param data 要插入的新数据* @return 插入的新元素(数据+引用)*/static DataLinkedList insertData(DataLinkedList head, String key, DataElement data) {DataLinkedList keyData = null;//查找key所在的位置DataLinkedList temp = head;while (temp != null) {DataElement date = temp.dataElement;if (date.key.equals(key)) {//节点的关键字与传入的关键字相同keyData = temp;//找到key的位置break;}temp = temp.nextNode;//处理下一节点}if (keyData != null) {DataLinkedList node = new DataLinkedList();//新追加的结点是在链表头部追加node.dataElement = data;//保存数据node.nextNode = keyData.nextNode;//新元素指向key所指向的下一个元素//如果是双向链表,keyData.nextNode的元素的previousNode也要指向新添加的元素node,//而新添加的node的previousNode也要指向keyDatakeyData.nextNode = node;//key指向新添加的元素return node;}return null;//未找到指定key所在的位置}/*** 链表结构中的查找算法* 一般来说可以通过关键字查询,从表头依次查找** @param head 链表表头* @param key 查找的关键字*/static DataLinkedList findData(DataLinkedList head, String key) {DataLinkedList temp = head;//保存表头指针while (temp != null) {//节点有效,进行查找DataElement date = temp.dataElement;if (date != null && date.key != null) {if (date.key.equals(key)) {//节点的关键字与传入的关键字相同return temp;//返回该节点指针}}temp = temp.nextNode;//处理下一节点}return null;//未找到,返回空指针}/*** 删除key对应的元素** @param head 表头* @param key 数据的关键字* @return 删除成功还是失败*/static boolean deleteNode(DataLinkedList head, String key) {DataLinkedList previousData = null;DataLinkedList temp = head;while (temp != null) {//节点有效,进行查找DataElement date = temp.dataElement;if (date != null && date.key != null) {if (date.key.equals(key)) {//节点的关键字与传入的关键字相同if (previousData != null) {previousData.nextNode = temp.nextNode;//将上一个节点的nextNode指向当前节点的下一个结点temp = null;//删除当前节点释放内存return true;}}}previousData = temp;temp = temp.nextNode;//处理下一节点}return false;//没有找到key对应的结点}/*** 获取链表长度** @param head 表头* @return 链表长度*/static int size(DataLinkedList head) {DataLinkedList temp = head;int size = 0;while (temp != null) {size++;//当前结点非空,数量+1temp = temp.nextNode;//处理下一节点}return size;}/*** 输出所有结点数据** @param head 表头*/static void showAllNode(DataLinkedList head) {DataLinkedList temp = head;while (temp != null) {//输出当前结点的数据DataElement date = temp.dataElement;if (date != null && date.key != null) {System.out.printf("data:{key:%s,otherMessage:%s}%n",date.key, date.other_messages);}temp = temp.nextNode;//处理下一节点}}}
调用:
DataLinkedList head = new DataLinkedList();head.dataElement = new DataElement("key1", "otherMessage1");head.nextNode = null;System.out.printf("开始在头部追加结点keyHead.%n");head = DataLinkedList.addHeadData(head, new DataElement("keyHead", "otherMessageHead"));if (head != null) {System.out.printf("链表头部追加结点成功!%n");}System.out.printf("开始在尾部追加结点key2.%n");DataLinkedList node = DataLinkedList.addData(head, new DataElement("key2", "otherMessage2"));if (node != null) {System.out.printf("链表尾部追加结点成功!%n");}System.out.printf("开始在key1后插入结点key3.%n");node = DataLinkedList.insertData(head, "key1", new DataElement("key3", "otherMessage3"));if (node != null) {System.out.printf("在key1后插入结点成功!%n");}System.out.printf("开始查找key2所在的结点,%n");node = DataLinkedList.findData(head, "key2");if (node != null) {System.out.printf("key2所在的结点:data:{key:%s,otherMessage:%s},nextNodeAddress:{%s}%n",node.dataElement.key, node.dataElement.other_messages, node.nextNode);}int size = DataLinkedList.size(head);System.out.printf("链表中总长度为:%d%n", size);System.out.printf("链表中的所有元素:%n");DataLinkedList.showAllNode(head);boolean delete = DataLinkedList.deleteNode(head, "key2");System.out.printf("链表中删除key2元素的结果:%b%n", delete);
输出:
开始在头部追加结点keyHead.链表头部追加结点成功!开始在尾部追加结点key2.链表尾部追加结点成功!开始在key1后插入结点key3.在key1后插入结点成功!开始查找key2所在的结点,key2所在的结点:data:{key:key2,otherMessage:otherMessage2},nextNodeAddress:{null}链表中总长度为:4链表中的所有元素:data:{key:keyHead,otherMessage:otherMessageHead}data:{key:key1,otherMessage:otherMessage1}data:{key:key3,otherMessage:otherMessage3}data:{key:key2,otherMessage:otherMessage2}链表中删除key2元素的结果:true
3.队列结构

由于队列结构比较简单,就不做详细的示例,代码可以参考下面的栈结构,将栈顶作为队头,并增加队尾标记,以及对队头、队尾的相关操作即可。
4.树结构




/*** 定义二叉树结构*/static class TreeDate {String data;TreeDate leftData;TreeDate rightData;/*** 初始化二叉树** @param rootData 根结点的数据* @return TreeDate二叉树的树根*/static TreeDate initTree(String rootData) {TreeDate treeRoot = new TreeDate();treeRoot.data = rootData;treeRoot.leftData = null;treeRoot.rightData = null;return treeRoot;}/*** 添加树结点** @param treeRoot 树的跟结点* @param parentData 要添加的结点位于哪个父结点下* @param leftOrRight 作为父结点的左子树(1)还是右子树(2)* @param nodeData 要添加的结点数据*/static void addNode(TreeDate treeRoot, String parentData, int leftOrRight, String nodeData) {TreeDate parentNode = findData(treeRoot, parentData);//根据key找到父结点if (parentNode != null) {TreeDate node = new TreeDate();node.data = nodeData;node.leftData = null;node.rightData = null;switch (leftOrRight) {case 1:if (parentNode.leftData != null) {System.out.printf("父节点的左子树不为空!");} else {parentNode.leftData = node;}break;case 2:if (parentNode.rightData != null) {System.out.printf("父节点的右子树不为空!");} else {parentNode.rightData = node;}break;}} else {System.out.printf("父节点[%s]未找到!", parentData);}}/*** 二叉树查找算法* 遍历二叉树中的每一个结点,逐个比较数据。** @param treeNode 树结点,首次调用传入树的根结点* @param data 要查找的结点* @return TreeDate 查找结果*/static TreeDate findData(TreeDate treeNode, String data) {if (treeNode == null) {return null;} else {if (treeNode.data.equals(data)) {return treeNode;}if (findData(treeNode.leftData, data) != null) {//递归查找左结点return findData(treeNode.leftData, data);}if (findData(treeNode.rightData, data) != null) {//递归查找右结点return findData(treeNode.rightData, data);}}return null;}/*** 获取左子树** @param treeNode 树的一个结点* @return 左子树*/static TreeDate leftNode(TreeDate treeNode) {if (treeNode != null) {return treeNode.leftData;}return null;}/*** 获取右子树** @param treeNode 树的一个结点* @return 右子树*/static TreeDate rightNode(TreeDate treeNode) {if (treeNode != null) {return treeNode.rightData;}return null;}/*** 判断空树** @param treeRoot 根结点* @return true空树,false不是空树*/static boolean isEmpty(TreeDate treeRoot) {return treeRoot == null;}/*** 计算二叉树的深度,计算二叉树的最大层数** @param treeNode 从根结点开始* @return 二叉树的最大层数int*/static int depth(TreeDate treeNode) {int depthLeft = 0, depthRight = 0;if (treeNode == null) {return 0;}if (treeNode.leftData != null) {depthLeft = depth(treeNode.leftData);}if (treeNode.rightData != null) {depthRight = depth(treeNode.rightData);}if (depthLeft > depthRight) {return depthLeft + 1;}return depthRight + 1;}/*** 清空二叉树* 对于JVM,这个clearTree,是unnecessary的,不必要的** @param treeNode 根结点*/static void clearTree(TreeDate treeNode) {if (treeNode != null) {clearTree(treeNode.leftData);clearTree(treeNode.rightData);treeNode = null;//释放内存}}/*** 显示当前结点的数据** @param treeNode 当前结点*/static void nodeData(TreeDate treeNode) {System.out.printf(" %s->", treeNode.data);}/*** 遍历二叉树(按层遍历)** @param treeRoot 根结点*/static void traverseTreeAccordingToLevel(TreeDate treeRoot) {int head = 0, tail = 0;final int MAX_LENGTH = 100;//树的最大结点数TreeDate p;TreeDate[] q = new TreeDate[MAX_LENGTH];//定义一个顺序栈if (treeRoot != null) {//如果队首引用不为空tail = (tail + 1) % MAX_LENGTH;//计算循环队列队尾序号q[tail] = treeRoot;//将二叉树根引用进队}while (head != tail) {//队列不为空,进行循环head = (head + 1) % MAX_LENGTH;//计算循环队列的队首序号p = q[head];//获取队首元素nodeData(p);//处理队首元素if (p.leftData != null) {//存在左子树tail = (tail + 1) % MAX_LENGTH;//计算循环队列的队尾序号q[tail] = p.leftData;//将左子树引用进队}if (p.rightData != null) {tail = (tail + 1) % MAX_LENGTH;q[tail] = p.rightData;}}}/*** 遍历二叉树(先序遍历算法)* 先按照中序遍历左子树,再访问根结点,最后按中序遍历右子树** @param treeRoot 根结点*/static void traverseTreeAccordingToPerorder(TreeDate treeRoot) {if (treeRoot != null) {nodeData(treeRoot);//显示结点数据traverseTreeAccordingToPerorder(treeRoot.leftData);traverseTreeAccordingToPerorder(treeRoot.rightData);}}/*** 遍历二叉树(中序遍历算法)* 先访问根结点,再按照先序遍历左子树,最后按先序遍历右子树** @param treeRoot 根结点*/static void traverseTreeAccordingToInorder(TreeDate treeRoot) {if (treeRoot != null) {traverseTreeAccordingToInorder(treeRoot.leftData);nodeData(treeRoot);//显示结点数据traverseTreeAccordingToInorder(treeRoot.rightData);}}/*** 遍历二叉树(后序遍历算法)* 先按照后序遍历左子树,再按后序遍历右子树,最后访问根结点** @param treeRoot 根结点*/static void traverseTreeAccordingToPostorder(TreeDate treeRoot) {if (treeRoot != null) {traverseTreeAccordingToPostorder(treeRoot.leftData);traverseTreeAccordingToPostorder(treeRoot.rightData);nodeData(treeRoot);//显示结点数据}}}
调用:
TreeDate rootNode = TreeDate.initTree("根结点");TreeDate.addNode(rootNode, "根结点", 1, "左结点1");TreeDate.addNode(rootNode, "根结点", 2, "右结点2");TreeDate.addNode(rootNode, "左结点1", 1, "左结点1.1");TreeDate.addNode(rootNode, "左结点1", 2, "右结点1.2");TreeDate.addNode(rootNode, "右结点2", 1, "左结点2.1");TreeDate.addNode(rootNode, "左结点2.1", 1, "左结点2.1.1");TreeDate node = TreeDate.findData(rootNode, "右结点2");if (node != null) {System.out.printf("找到右结点2,");TreeDate leftNode = TreeDate.leftNode(node);TreeDate rightNode = TreeDate.rightNode(node);if (leftNode != null) {System.out.printf("其左子树为:%s\t", leftNode.data);}if (rightNode != null) {System.out.printf("右子树为:%s", rightNode.data);}System.out.printf("%n");}int depth = TreeDate.depth(rootNode);System.out.printf("树深度:%d%n", depth);System.out.printf("按层遍历算法:%n");TreeDate.traverseTreeAccordingToLevel(rootNode);System.out.printf("%n先序遍历算法:%n");TreeDate.traverseTreeAccordingToPerorder(rootNode);System.out.printf("%n中序遍历算法:%n");TreeDate.traverseTreeAccordingToInorder(rootNode);System.out.printf("%n后序遍历算法:%n");TreeDate.traverseTreeAccordingToPostorder(rootNode);System.out.printf("%n");boolean isEmpty = TreeDate.isEmpty(rootNode);System.out.printf("是否是空树:%b%n", isEmpty);System.out.printf("清空树%n");TreeDate.clearTree(rootNode);
输出:
找到右结点2,其左子树为:左结点2.1树深度:4按层遍历算法:根结点-> 左结点1-> 右结点2-> 左结点1.1-> 右结点1.2-> 左结点2.1-> 左结点2.1.1->先序遍历算法:根结点-> 左结点1-> 左结点1.1-> 右结点1.2-> 右结点2-> 左结点2.1-> 左结点2.1.1->中序遍历算法:左结点1.1-> 左结点1-> 右结点1.2-> 根结点-> 左结点2.1.1-> 左结点2.1-> 右结点2->后序遍历算法:左结点1.1-> 右结点1.2-> 左结点1-> 左结点2.1.1-> 左结点2.1-> 右结点2-> 根结点->是否是空树:false清空树
5.栈结构

/*** 定义数据结构中的元素*/static class DataElement {String key;String other_messages;public DataElement(String key, String other_messages) {this.key = key;this.other_messages = other_messages;}}/*** 定义栈结构*/static class Stack {static final int MAX_LENGTH = 10;//当前栈表的最大长度DataElement[] stackData = new DataElement[MAX_LENGTH];//栈表数据int top;//栈顶/*** 初始化栈*/static Stack initStack() {Stack s = new Stack();s.top = 0;return s;}/*** 判断栈是否为空** @param stack Stack* @return true是空栈,false栈不为空*/static boolean isEmpty(Stack stack) {return stack.top == 0;}/*** 栈是不是已满** @param stack Stack* @return true栈已满,false栈未满*/static boolean isFull(Stack stack) {return stack.top == MAX_LENGTH;}/*** 清空栈** @param stack Stack*/static void clear(Stack stack) {stack.top = 0;}/*** 释放栈所占空间** @param stack Stack*/static void free(Stack stack) {if (stack != null) {stack = null;}}/*** 入栈** @param stack Stack* @param element DataElement* @return 0入栈失败,1入栈成功*/static int push(Stack stack, DataElement element) {if (stack.top + 1 > MAX_LENGTH) {System.out.printf("栈溢出!");return 0;}stack.stackData[++stack.top] = element;//将element入栈,入栈后栈顶(top)+1return 1;}/*** 出栈** @param stack Stack* @return 栈顶的DataElement*/static DataElement pop(Stack stack) {if (stack.top == 0) {System.out.print("栈已空!");return null;}return stack.stackData[stack.top--];//将栈顶元素出栈,出栈后栈顶(top)-1}/*** 查看栈数据,由于栈只能操作栈顶数据,所以查看栈的数据只能看栈顶的元素** @param stack Stack* @return 栈顶的DataElement*/static DataElement peek(Stack stack) {if (stack.top == 0) {System.out.print("栈已空!");return null;}return stack.stackData[stack.top];}}
调用:
Stack stack = Stack.initStack();System.out.printf("栈是不是空栈:%b%n", Stack.isEmpty(stack));DataElement element = new DataElement("s1", "s1_other");int push = Stack.push(stack, element);System.out.printf("入栈结果:%d%n", push);element = new DataElement("s2", "s2_other");push = Stack.push(stack, element);System.out.printf("入栈结果:%d%n", push);element = new DataElement("s3", "s3_other");push = Stack.push(stack, element);System.out.printf("入栈结果:%d%n", push);System.out.printf("栈是不是空栈:%b%n", Stack.isEmpty(stack));System.out.printf("栈是不是满栈:%b%n", Stack.isFull(stack));DataElement dataElement = Stack.peek(stack);assert dataElement != null;System.out.printf("查看栈:{%s,%s}%n", dataElement.key, dataElement.other_messages);dataElement = Stack.pop(stack);assert dataElement != null;System.out.printf("出栈:{%s,%s}%n", dataElement.key, dataElement.other_messages);dataElement = Stack.pop(stack);assert dataElement != null;System.out.printf("出栈:{%s,%s}%n", dataElement.key, dataElement.other_messages);Stack.clear(stack);System.out.printf("清空栈后栈是不是空栈:%b%n", Stack.isEmpty(stack));Stack.free(stack);//释放栈空间
输出:
栈是不是空栈:true入栈结果:1入栈结果:1入栈结果:1栈是不是空栈:false栈是不是满栈:false查看栈:{s3,s3_other}出栈:{s3,s3_other}出栈:{s2,s2_other}清空栈后栈是不是空栈:true
6.图结构



图结构的代码参考 常用算法指南(三)查找算法 :5. 图结构中的查找算法
