数据结构(栈,队列,链表,二叉树)

栈作为一种数据结构,用途十分广泛。在回调函数等许多场景中都有应用。我们需要了解它的基本用途,那就是先进后出和队列的先进先出正好相反。

最近在学习数据结构和算法,于是自己来实现。我特别喜欢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();

 

 

查看结果: 
这里写图片描述

这里写图片描述

 

版权声明:本文为博主原创文章,未经博主允许不得转载。 http://blog.csdn.net/u013766436/article/details/51316403

posted @ 2018-03-20 13:08  栗子·无限意志  阅读(704)  评论(0编辑  收藏  举报