三种最常用的数据结构 - 线性表 、树 、堆

数据结构概念(逻辑结构和物理结构)

数据结构概念:把数据元素按照一定的关系组织起来的集合,用来组织和存储数据

数据结构分类:数据结构分为逻辑结构物理结构两大类

逻辑结构分类:集合结构、线性结构、树形结构、图形结构

a.集合结构:集合结构中数据元素除了属于同一个集合外,他们之间没有任何其他的关系

b.线性结构:线性结构中的数据元素之间存在一对一的关系

c.树形结构:树形结构中的数据元素之间存在一对多的层次关系

d. 图形结构:图形结构的数据元素是多对多的关系

物理结构分类:逻辑结构在计算机中真正的表示方式(又称为映像)称为物理结构,也可以叫做存储结构。常见的物理结构有顺序存储结构链式存储结构

a.顺序存储结构:把数据元素放到地址连续的存储单元里面,其数据间的逻辑关系和物理关系是一致的 ,比如我们常用的数组就是顺序存储结构。

b.链式存储结构:是把数据元素存放在任意的存储单元里面,这组存储单元可以是连续的也可以是不连续的。此时,数据元素之间并不能反映元素间的逻辑关系,因此在链式存储结构中引进了一个指针存放数据元素的地址,这样通过地址就可以找到相关联数据元素的位置

顺序存储结构--查询块,增删慢

链式存储结构--查询慢,增删块

线性表

线性表中数据存储的方式可以是顺序存储,也可以是链式存储,按照数据的存储方式不同,可以把线性表分为顺序表和链表。基于线性表,又可以实现栈和队列。

顺序表

顺序表是在计算机内存中以数组的形式保存的线性表,线性表的顺序存储是指用一组地址连续的存储单元,依次存储线性表中的各个元素、使得线性表中再逻辑结构上响铃的数据元素存储在相邻的物理存储单元中,即通过数据元素物理存储的相邻关系来反映数据元素之间逻辑上的相邻关系。

  • 顺序表的查找效率很高,根据索引可以直接定位,查找的时间复杂度为O(1),但是插入和删除的时候需要移动其他的元素,所以效率较低,比如,在位置i处插入,需要把i位置后面的元素依次向右移动一次,因此插入和删除的时间复杂度为O(n)。
  • 由于顺序表的底层由数组实现,数组的长度是固定的,所以在操作的过程中涉及到了容器扩容操作,需要更换新的更大的数组,把数据从就旧的数组中拷贝到新的数组中,这个过程比较耗时
  • JavaArrayList集合的底层也是一种顺序表,使用数组实现,同样提供了增删改查以及扩容等功能。

链表

链表是一种物理存储单元上非连续、非顺序的存储结构,其物理结构不能只管的表示数据元素的逻辑顺序,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列的结点(链表中的每一个元素称为结点)组成,结点可以在运行时动态生成。

 在使用链表的时候,需要先创建一个节点类

public class Node<T> {
	//存储元素
	public T item;
	//指向下一个结点
	public Node next;
	public Node(T item, Node next) {
		this.item = item;
		this.next = next;
	}
}

生成链表: 

public static void main(String[] args) throws Exception {
    //构建结点
    Node<Integer> first = new Node<Integer>(11, null);
    Node<Integer> second = new Node<Integer>(13, null);
    Node<Integer> third = new Node<Integer>(12, null);
    Node<Integer> fourth = new Node<Integer>(8, null);
    Node<Integer> fifth = new Node<Integer>(9, null);
    //生成链表
    first.next = second;
    second.next = third;
    third.next = fourth;
    fourth.next = fifth;
}

 栈

栈是一种基于先进后出 (FILO)的数据结构,是一种只能在一端进行插入和删除操作的特殊线性表。它按照先进后出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据(最后一个数据被第一个读出来)
我们称数据进入到栈的动作为 压栈 ,数据从栈中出去的动作为 弹栈

既可以使用顺序表实现栈,也可以用链表实现栈

使用数组,实现一个简单的不可扩容的栈:

public class stackDemo<m> {
	 m[] a;
	 int f=-1;
	 public stackDemo(int num)
	 {
		 a=(m[])new Object[num];
	 }
	 
	 public void add(m n)
	 {
		 if(f==a.length-1)
		 {
			 System.out.println("栈满");
			 return;
		 }
		 f++;
		 a[f]=n;
	 }
	 
	 public m get()
	 {
		 if(f==-1) 
		 {
			 System.out.println("栈空!");
			 return null;
		 }
		 m temp=a[f];
		 f--;
		 return temp;
	 }
	 
	 public static void main(String[] args) {
		stackDemo s=new stackDemo(5);
		String[] str=new String[] {"123","g","e","g","w","f","h","r"};
		for(int i=0;i<7;i++) s.add(str[i]);
		for(int i=0;i<7;i++) System.out.println(s.get());
	}
	 
}

 队列

队列是一种基于先进先出 (FIFO) 的数据结构,是一种只能在一端进行插入 ,在另一端进行删除操作的特殊线性表,它按照先进先出的原则存储数据,先进入的数据,在读取数据时先被读出来。

 同样的,既可以使用顺序表实现队列,也可以用链表实现队列

public class columnDemo <m>{	
	m[] a;
	int b=0;
	int e=0;
	
	public columnDemo(int num)
	{
		a=(m[])new Object[num];
	}
	
	public void add(m value)
	{
		if(e==a.length)
		{
			System.out.println("队列已满!");
			return;
		}
		a[e]=value;
		e++;
	}
	
	public m get()
	{
		if(b==e)
		{
			System.out.println("队空!");
			return null;
		}
		int n=b;
		b++;
		return a[n%a.length];
	}
	
	public static void main(String[] args) {
		columnDemo c=new columnDemo(5);
		String[] str=new String[] {"123","g","e","g","w","f","h","r"};
		for(int i=0;i<7;i++) c.add(str[i]);
		for(int i=0;i<7;i++) System.out.println(c.get());
	}
	
}

树的基本定义

树是由 n n>=1 )个有限结点组成一个具有层次关系的集合。把它叫做 ”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的 

二叉树

结点的度: 一个结点含有的子树的个数称为该结点的度;
二叉树就是度不超过 2 的树 ( 每个结点最多有两个子结点 )

二叉树的节点类代码: 
private class Node<Key,Value>{
    //存储键
    public Key key;
    //存储值
    private Value value;
    //记录左子结点
    public Node left;
    //记录右子结点
    public Node right;

    public Node(Key key, Value value, Node left, Node right) {
        this.key = key;
        this.value = value;
        this.left = left;
        this.right = right;
    }
}

满二叉树

 一个二叉树,如果每一个层的结点树都达到最大值,则这个二叉树就是满二叉树。

完全二叉树 

叶节点只能出现在最下层和次下层,并且最下面一层的结点都集中在该层最左边的若干位置的二叉树

二叉树的遍历

  1. 前序遍历:先访问根结点,然后再访问左子树,最后访问右子树
  2. 中序遍历:先访问左子树,中间访问根节点,最后访问右子树
  3. 后序遍历:先访问左子树,再访问右子树,最后访问根节点
如果我们分别对下面的树使用三种遍历方式进行遍历,得到的结果如下:

前序遍历代码:

//使用前序遍历,获取整个树中的所有键
public Queue<Key> preErgodic(){
    Queue<Key> keys = new Queue<>();
    preErgodic(root,keys);
    return keys;
}

//使用前序遍历,把指定树x中的所有键放入到keys队列中
private void preErgodic(Node x,Queue<Key> keys){
    if (x==null){
        return;
    }
    //1.把当前结点的key放入到队列中;
    keys.enqueue(x.key);
    //2.找到当前结点的左子树,如果不为空,递归遍历左子树
    if (x.left!=null){
        preErgodic(x.left,keys);
    }
    //3.找到当前结点的右子树,如果不为空,递归遍历右子树
    if (x.right!=null){
        preErgodic(x.right,keys);
    }
}

中序遍历代码:

//使用中序遍历,获取整个树中的所有键
public Queue<Key> midErgodic(){
    Queue<Key> keys = new Queue<>();
    midErgodic(root,keys);
    return keys;
}
//使用中序遍历,把指定树x中的所有键放入到keys队列中
private void midErgodic(Node x,Queue<Key> keys){
    if (x==null){
        return;
    }
    //1.找到当前结点的左子树,如果不为空,递归遍历左子树
    if (x.left!=null){
        midErgodic(x.left,keys);
    }
    //2.把当前结点的key放入到队列中;
    keys.enqueue(x.key);
    //3.找到当前结点的右子树,如果不为空,递归遍历右子树
    if (x.right!=null){
        midErgodic(x.right,keys);
    }
}

后序遍历代码:

//使用后序遍历,获取整个树中的所有键
public Queue<Key> afterErgodic(){
    Queue<Key> keys = new Queue<>();
    afterErgodic(root,keys);
    return keys;
}

//使用后序遍历,把指定树x中的所有键放入到keys队列中
private void afterErgodic(Node x,Queue<Key> keys){
    if (x==null){
        return;
    }
    //1.找到当前结点的左子树,如果不为空,递归遍历左子树
    if (x.left!=null){
        afterErgodic(x.left,keys);
    }
    //2.找到当前结点的右子树,如果不为空,递归遍历右子树
    if (x.right!=null){
        afterErgodic(x.right,keys);
    }
    //3.把当前结点的key放入到队列中;
    keys.enqueue(x.key);
}

堆的定义

堆是计算机科学中一类特殊的数据结构的统称,堆通常可以被看做是一棵完全二叉树的数组对象

堆的特性

  1. 它是完全二叉树,除了树的最后一层结点不需要是满的,其它的每一层从左到右都是满的,如果最后一层结点不是满的,那么要求左满右不满。
  2. 它通常用数组来实现。具体方法就是将二叉树的结点按照层级顺序放入数组中,根结点在位置1,它的子结点在位置23,而子结点的子 结点则分别在位置4,5,6和7,以此类推。如果一个结点的位置为k,则它的父结点的位置为[k/2],而它的两个子结点的位置则分别为2k和2k+1。这样,在不使用指针的情况下,我们也可以通过计算数组的索引在树中上下移动:从a[k]向上一层,就令k等于k/2,向下一层就令k等于2k2k+1
  3. 每个结点都大于等于它的两个子结点。这里要注意堆中仅仅规定了每个结点大于等于它的两个子结点,但这两个子结点的顺序并没有做规定,跟我们之前学习的二叉查找树是有区别的。

堆排序

 堆可以分为大根堆,小根堆

大根堆:每个节点的值都大于或者等于他的左右孩子节点的值

小根堆:每个结点的值都小于或等于其左孩子和右孩子结点的值0

堆是用数组完成数据元素的存储的,由于数组的底层是一串连续的内存地址,所以我们要往堆中插入数据,我们只能往数组中从索引0处开始,依次往后存放数据,但是堆中对元素的顺序是有要求的,每一个结点的数据要大于等于它的两个子结点的数据,所以每次插入一个元素,都会使得堆中的数据顺序变乱,这个时候我们就需要通过一些方法让刚才插入的这个数据放入到合适的位置。
以大根堆的调整为例:

同样,删除元素也需要进行堆调整:

对于大根堆,索引1处的元素,也就是根结点是最大的元素,当我们把根结点的元素删除后,需要有一个新的根结点出现,这时我们可以暂时把堆中最后一个元素放到索引1处,充当根结点,但是它不满足大根堆的有序性需求,这个时候我们就需要通过一些方法,让这个新的根结点放入到合适的位置。

堆排序实现步骤(使用大根堆,实现从小到大排序):
  1. 构造大根堆;
  2. 得到堆顶元素,这个值就是最大值;
  3. 交换堆顶元素和数组中的最后一个元素,此时所有元素中的最大元素已经放到合适的位置;
  4. 对堆进行调整,重新让除了最后一个元素的剩余元素中的最大值放到堆顶;
  5. 重复2~4这个步骤,直到堆中剩一个元素为止;
public class HeapSort {
	public static void main(String[] args) {
		int[] a=new int[]{5,32,4,23,1,8,64,67,35,85,12,123,543,45,23,10};
		System.out.println(Arrays.toString(a));
		int l=a.length;
		for(int i=0;i<a.length-1;i++)
		{
			for(int p=l-1;p>=0;p--)
			{
				sort(a,p,l);
			}
			int temp=a[0];
			a[0]=a[l-1];
			a[l-1]=temp;
			l--;
		}
		System.out.println(Arrays.toString(a));
	}
	
	public static void sort(int[] a,int p,int l)
	{
		int c=2*p+1;
		if(c<l)
		{
			int rc=c+1;
			if(rc<l && a[c]<a[rc]) c=rc;
			if(a[p]<a[c])
			{
				int temp=a[p];
				a[p]=a[c];
				a[c]=temp;
				p=c;
				sort(a,p,l);
			}
 		}
	}
	
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值