栈
栈作为一种数据结构,用途十分广泛。在回调函数等许多场景中都有应用。我们需要了解它的基本用途,那就是先进后出和队列的先进先出正好相反。
最近在学习数据结构和算法,于是自己来实现。我特别喜欢C语言的指针,我发现很好用,于是用C++来实现一个简单的范例。
主要实现就是函数就是Pop,Push
Push将数据放到一个到顶层位置。
Pop将数据从已有的数据中取出来。
Stack.h文件,主要描述里面的数据,数据我用整形来处理,这个也可以是其他,只是示范
typedef struct mData
{
int data;
mData *next;
}Data;
class Stack
{
public:
Stack();
~Stack();
void Push(int data);
int Pop();
void CreateNode(int data);
void Clear();
int getSize();
private:
Data * pop;
int size;
};
Stack::Stack()
{
pop = nullptr;
size = 0;
}
Stack::~Stack()
{
}
实现的代码:
Stack.cpp
#include"Stack.h"
#include "iostream"
using namespace std;
//创建一个新的结点,并放在顶层
void Stack::CreateNode(int data){
Data *p = new Data;
if (p == nullptr){
printf("新建失败");
return;
}
pop = p;
p->data = data;
pop->next = nullptr;
size++;
}
//将新的数据放在顶层,如果顶层不为空,需要将原本的顶层下移,变为内部的next的指向
void Stack::Push(int data){
Data * p = pop;
if (pop == nullptr)
CreateNode(data);
else{
CreateNode(data);
pop->next = p;
}
}
//获得当前的栈的个数
int Stack::getSize()
{
return size;
}
//从栈顶取出一个数据,并删除顶层内存,将底层位置上移
int Stack::Pop()
{
Data *p = pop;
if (pop == nullptr){
printf("数据没有");
return -10000;
}
else{
pop = pop->next;
int data = p->data;
delete p;
size--;
return data;
}
}
//清空数据和内存
void Stack::Clear()
{
while(pop!= nullptr){
Data* p = pop;
pop = pop->next;
size--;
delete p;
}
}![这里写图片描述](http://img.blog.csdn.net/20160504170135374)
int main(){
Stack *stack = new Stack();
int data;
stack->Push(5);
printf("%d\n", stack->getSize());
stack->Push(3);
printf("%d\n", stack->getSize());
stack->Push(2);
printf("%d\n", stack->getSize());
data =stack->Pop();
printf("%d,%d\n\n", stack->getSize(),data);
data = stack->Pop();
printf("%d,%d\n\n", stack->getSize(), data);
data = stack->Pop();
printf("%d,%d\n\n", stack->getSize(), data);
stack->Clear();
printf("%d\n", stack->getSize());
data =stack->Pop();
printf("%d,%d\n\n", stack->getSize(), data);
getchar();
return 0;
}
执行效果如图:
可以看到一开始大小在增加,然后数据取出一次是2,3,5正好和Push的方式相反。正好就是先进后出。
队列
队列作为一种数据结构,它的特点是先进先出,就相当于排队一样,在我们的生活中许多现象都是由此构成。它的特点就是有序前行,后来的排在最后。
看下代码实现:
class Queue
{
public:
Queue();
~Queue();
//进入队列
void EnQueue(int data);
//出来队列
int DeQueue();
//判断是否为空
bool IsQueueEmpty();
//获得尺寸
int getSize();
void Clear();
private:
int size;
Data *pop;
};
{
if (pop == nullptr){
Data* p = new Data;
if (p == nullptr){
printf("新建失败");
return;
}
pop = p;
pop->data = data;
size++;
pop->next = nullptr;
}
else{
//需要先判断是否它的下个结点是为空,否则指的就是空指针,指针必须要有对应的地址,才能在次基础上继续进行
Data* temp = pop;
while (temp->next != nullptr){
temp = temp->next;
}
temp->next = new Data;
if (temp == nullptr){
printf("新建失败");
return;
}
temp->next->data = data;
size++;
temp->next->next = nullptr;
}
}
void Queue::Clear()
{
while (pop != nullptr)
{
size--;
Data * p = pop;
pop = pop->next;
delete p;
}
}
int Queue::DeQueue()
{
if (pop != nullptr){
size--;
Data * p = pop;
int data = pop->data;
pop = pop->next;
delete p;
return data;
}
else{
printf("没有数据了");
return -10000000;
}
}
int Queue::getSize(){
return size;
}
bool Queue::IsQueueEmpty(){
if (size == 0)
return true;
else
return false;
}
其实实现的过程要抓紧其数据的构成,如何去遍历,将每个结点里面的指针对应即可,但是由于我在中间导致了一个错误:错误的将temp判断,而不是temp->next,导致temp一开始是指向空指针,导致了很长时间调试,最后发现指向空指针后,temp无法和一开始pop指针相连接,这是很重要,在进行两个指针的连接关系时,必须要有一个指针是有具体的内存的,否则不能建立连接。
调用:
int main(){
Queue *queue = new Queue();
queue->EnQueue(5);
printf("%d\n", queue->getSize());
queue->EnQueue(3);
printf("%d\n", queue->getSize());
queue->EnQueue(2);
printf("%d\n", queue->getSize());
int data2 =queue->DeQueue();
printf("%d,%d\n", data2,queue->getSize());
data2 = queue->DeQueue();
printf("%d,%d\n", data2, queue->getSize());
data2 = queue->DeQueue();
printf("%d,%d\n", data2, queue->getSize());
queue->Clear();
getchar();
return 0;
}
出现结果:
可以看到size在一开始增加,后面逐渐减少,一开始的数据顺序是5,3,2,现在能看到数据取出顺序还是5,3,2,符合先进先出的原则。
链表
链表作为最基本的数据结构,有许多应用,在java的类集中,通过分析链表来获得理解,许多类的使用的底层内容都和链表有关。
其实链表准确地说,就是动态数组。
在内存中,每个结点有两个关键的地方,第一个就是链表结点中存储的数据,还有一个就是链表结点中,会存储下个结点的地址或者引用。
通俗地说,就是每个结点被放在内存中,通过一个根结点将他们一一串起来构成整个链表。
看java实现的一个链表的代码:
package DataStruct;
import javax.swing.RootPaneContainer;
import org.omg.CosNaming.NamingContextExtPackage.StringNameHelper;
//链表工具类
class Link {
// 结点
private class Node {
// 结点数据
private String data;
// 下个结点指针
private Node next;
// 初始化数据
public Node(String data) {
this.data = data;
}
// 当前this第一次为this.root
// 第二次执行时this为 this.root.next
// 第三次执行时this为 this.root.next.next
public void addNode(Node newNode) {
if (this.next == null)
this.next = newNode;
else
this.next.addNode(newNode);
}
public void printNode() {
System.out.println(this.data);
if (this.next != null) {
this.next.printNode();
}
}
public boolean containsNode(String data) {
if (this.data.equals(data)) {
return true;
} else {
if (this.next != null)
return this.next.containsNode(data);
else
return false;
}
}
// 获得数据
public String getNode(int index) {
if (Link.this.foot++ == index) {
return this.data;
} else {
return this.next.getNode(index);
}
}
// 修改数据
public void setNode(int index, String data) {
if (Link.this.foot++ == index) {
this.data = data;
} else {
this.next.setNode(index, data);
}
}
// 要传递上个结点的引用
public void removeNode(Node previous, String data) {
if (data.equals(this.data)) {
previous.next = this.next;
} else {
this.next.removeNode(this, data);
}
}
public void toArrayNode() {
Link.this.retArray[Link.this.foot++] = this.data;
if (this.next != null) {
this.next.toArrayNode();
}
}
}
// ===================上面为内部类======================
// 保存根结点
private Node root;
private int count = 0;
private int foot = 0;
private String[] retArray;
public Link() {
root = null;
}
public void add(String data) {
if (data == null)
return;
Node newNode = new Node(data);
if (root == null) {
root = newNode;
} else {
this.root.addNode(newNode);
}
this.count++;
}
// 链表大小
public int size() {
return this.count;
}
public void print() {
if (this.root != null) {
this.root.printNode();
}
}
// 判断是否为空链表
public boolean isEmpty() {
if (this.count == 0)
return true;
return false;
}
// 判断数据是否存在
public boolean contains(String data) {
if (data == null || this.root == null) {
return false;
}
return this.root.containsNode(data);
}
// 根据索引来获取数据
public String get(int index) {
if (index >= this.count) {
return null;
}
this.foot = 0;
// 查询过程中,需要在Node中查询
return this.root.getNode(index);
}
// 根据索引修改数据
public void set(int index, String data) {
if (index >= this.count) {
return;
}
this.foot = 0;
this.root.setNode(index, data);
}
// 数据删除
public void remove(String data) {
if (this.contains(data)) {
// 内部类可以访问私有属性,判断是否为跟元素
if (data.equals(this.root.data)) {
this.root = this.root.next;
} else {
this.root.next.removeNode(this.root, data);
}
this.count--;
}
}
// 链表以数组返回
public String[] toArray() {
if (this.root == null) {
return null;
}
this.foot = 0;
this.retArray = new String[this.count];
this.root.toArrayNode();
return this.retArray;
}
}
public class TestList {
public static void main(String arg[]) {
Link link = new Link();
link.add("fwef");
link.add("毛毛");
link.add("问题");
link.add("啥问题");
// link.set(3, "data");
link.remove("fwef");
link.print();
String[] data = link.toArray();
for (int i = 0; i < data.length; i++) {
System.out.println(data[i]);
}
}
}
这里通过内部类更加方便地构造,因为在程序里面可以直接访问私有属性。而不需要在写对应的方法。
二叉树
二叉树的应用比较广泛,也是很重要的一种数据结构,在面试以及许多地方都可能用得到。主要讲下,我自己写的二叉树的代码。
首先,我们通过建立一个类来操作二叉树
为二叉树添加一个元素,我这个类里面实现的是,每个结点都需要添加两个元素,除了根元素以外。
比如现在有个数组,里面内容需要从1-9元素,建立的最终结果就是:
这里面我们需要通过四种方法来遍历每个元素:(命名规则其实是根据根结点先后顺序命名的)
先序遍历,就是先根结点,然后左结点,最后右结点。124895367
中序遍历,先左结点,然后根结点,最后右结点。849251637
后序遍历,先左结点,然后右结点,最后根结点。894526731
逐层遍历:每个层开始遍历,123456789,逐层遍历,用了队列的先入先出的特性,保存了数据。
类的方法和信息
typedef struct tree
{
int data;
tree * right;
tree * left;
}Tree;
class Twotree
{
public:
void createNode(int data);
void add(Tree * T,int data);
void frontOrder(Tree *t); //前序遍历,先根,后左,在右,根据根结点位置来区分遍历形式
void middleOrder(Tree *t); //中序遍历,先左,后根,在右
void behindOrer(Tree *t); //后序遍历,先左,再右,后根
void floorOrder(); //通过队列来逐层遍历
Tree *getRoot(); //获得根结点
int getSize(); //获得元素个数
Twotree();
~Twotree();
private:
Tree * root;
int size;
Queue *queue;
};
Twotree::Twotree()
{
queue = new Queue();
size = 0;
root = nullptr;
}
Twotree::~Twotree()
{
}
类的具体实现:
Tree* Twotree::getRoot()
{
return this->root;
}
void Twotree::createNode(int data){
if (root == nullptr){
root = new Tree;
if (root == nullptr)
{
printf("创建失败");
return;
}
//把数据放在队列中
queue->EnQueue(data);
size++;
root->data = data;
root->left = nullptr;
root->right = nullptr;
}
}
void Twotree::add(Tree * T, int data){
//如果左子树为空,则添加左子树
if (T->left == nullptr){
Tree *temp = new Tree;
if (temp == nullptr){
printf("创建失败!");
}
queue->EnQueue(data);
T->left = temp;
T->left->data = data;
size++;
T->left->left = nullptr;
T->left->right = nullptr;
return;
}
else if (T->right == nullptr){
Tree *temp = new Tree;
if (temp == nullptr){
printf("创建失败!");
}
queue->EnQueue(data);
T->right = temp;
T->right->data = data;
T->right->left = nullptr;
T->right->right = nullptr;
size++;
return;
}
//如果右子树不为空,并且下个节点的左子树或者右子树为空,做需要建立下个节点左子树或者右子树。
//如果左右子树的下个结点都完成了分配,那么就需要从左子树开始
else if ((T->right != nullptr && (T->left->left == nullptr || T->left->right == nullptr)) || (T->right != nullptr&&T->right->left!= nullptr&&T->right->right != nullptr)){
add(T->left, data);
return;
}
else {
add(T->right, data);
return;
}
}
void Twotree::frontOrder(Tree *t){
if (t == nullptr){
return;
}
//先输出根结点
printf("%d\n", t->data);
frontOrder(t->left);
frontOrder(t->right);
return;
}
void Twotree::middleOrder(Tree *t){
if (t != nullptr){
middleOrder(t->left);
//中输出左结点
printf("%d\n", t->data);
middleOrder(t->right);
}
}
void Twotree::behindOrer(Tree *t){
if (t != nullptr){
behindOrer(t->left);
behindOrer(t->right);
//后输出根结点
printf("%d\n", t->data);
}
}
int Twotree::getSize(){
return this->size;
}
void Twotree::floorOrder(){
printf("************逐层遍历*****************\n");
for (int i = 0; i < this->size;i++)
{
int data = queue->DeQueue();
printf("%d\n", data);
}
}
这里面用到的队列就是上面程序队列的原型。请查看队列的介绍。
对于没有用递归的方法,有人建议使用栈的先入后出特性来解决问题。
但我觉得递归方法更加费脑袋来理解,是个很不错的练习方式。
调用:
Twotree *twotree = new Twotree();
twotree->createNode(1);
twotree->add(twotree->getRoot(),2);
twotree->add(twotree->getRoot(), 3);
twotree->add(twotree->getRoot(), 4);
twotree->add(twotree->getRoot(), 5);
twotree->add(twotree->getRoot(), 6);
twotree->add(twotree->getRoot(), 7);
twotree->add(twotree->getRoot(), 8);
twotree->add(twotree->getRoot(), 9);
printf("%d\n",twotree->getSize());
printf("***********前序遍历*************\n");
twotree->frontOrder(twotree->getRoot());
printf("***********中序遍历*************\n");
twotree->middleOrder(twotree->getRoot());
printf("************后序遍历************\n");
twotree->behindOrer(twotree->getRoot());
twotree->floorOrder();
查看结果: