29数据结构及算法大合集

最广、最深 遍历
排序算法

常见的数据结构

数据结构是计算机存储、组织数据的方式。对于特定的数据结构(比如数组),有些操作效率很高(读某个数组元素),有些操作的效率很低(删除某个数组元素)。程序员的目标是为当前的问题选择最优的数据结构。
有8种常见的数据结构

  • 1、数组
  • 2、栈
  • 3、队列
  • 4、链表
  • 5、图
  • 6、树
  • 7、前缀树
  • 8、哈希表

数组

数组(Array)大概是最简单,也是最常用的数据结构了。其他数据结构,比如栈和队列都是由数组衍生出来的。
在这里插入图片描述
每一个数组元素的位置由数字编号,称为下标或者索引(index)。大多数编程语言的数组第一个元素的下标是 0。
根据维度区分,有 2 种不同的数组:

  • 一维数组(如上图所示)
  • 多维数组(数组的元素为数组)
数组的基本操作
  • Insert - 在某个索引处插入元素
  • Get - 读取某个索引处的元素
  • Delete - 删除某个索引处的元素
  • Size - 获取数组的长度

栈中的元素采用 LIFO (Last In First Out),即后进先出。
撤回,即 Ctrl+Z,是我们最常见的操作之一,大多数应用都会支持这个功能。你知道它是怎么实现的吗?答案是这样的:把之前的应用状态(限制个数)保存到内存中,最近的状态放到第一个。这时,我们需要栈(stack)来实现这个功能。
在这里插入图片描述

栈的基本操作
  • Push —  在栈的最上方插入元素
  • Pop — 返回栈最上方的元素,并将其删除
  • isEmpty —  查询栈是否为空
  • Top —  返回栈最上方的元素,并不删除

队列

队列(Queue)与栈类似,都是采用线性结构存储数据。它们的区别在于,栈采用 LIFO 方式,而队列采用先进先出,即FIFO(First in First Out)
在这里插入图片描述

队列的基本操作
  • Enqueue —  在队列末尾插入元素
  • Dequeue —  将队列第一个元素删除
  • isEmpty —  查询队列是否为空
  • Top —  返回队列的第一个元素

链表

链表(Linked List)也是线性结构,它与数组看起来非常像,但是它们的内存分配方式、内部结构和插入删除操作方式都不一样。
链表是一系列节点组成的链,每一个节点保存了数据以及指向下一个节点的指针。链表头指针指向第一个节点,如果链表为空,则头指针为空或者为 null。
链表可以用来实现文件系统、哈希表和邻接表
链表可以分为单向链表和双向链表。
在这里插入图片描述

链表的基本操作
  • InsertAtEnd —  在链表结尾插入元素
  • InsertAtHead —  在链表开头插入元素
  • Delete —  删除链表的指定元素
  • DeleteAtHead —  删除链表第一个元素
  • Search —  在链表中查询指定元素
  • isEmpty —  查询链表是否为空

图(graph)由多个节点(vertex)构成,节点之间可以互相连接组成一个网络。(x, y)表示一条边(edge),它表示节点 x 与 y 相连。边可能会有权值(weight/cost)。
在这里插入图片描述
图可以分为:

  • 有向图
  • 无向图

遍历图有两种算法:

  • 广度优先搜索(Breadth First Search)
  • 深度优先搜索(Depth First Search)

树(Tree)是一个分层的数据结构,由节点和连接节点的边组成。树是一种特殊的图,它与图最大的区别是没有循环
树被广泛应用在人工智能和一些复杂算法中,用来提供高效的存储结构。在这里插入图片描述
树有很多分类:

  • N 叉树(N-ary Tree)
  • 平衡树(Balanced Tree)
  • 二叉树(Binary Tree)
  • 二叉查找树(Binary Search Tree)
  • 平衡二叉树(AVL Tree)
  • 红黑树(Red Black Tree)
  • 2-3 树(2–3 Tree)

前缀树

前缀树(Prefix Trees 或者 Trie)与树类似,用于处理字符串相关的问题时非常高效。它可以实现快速检索,常用于字典中的单词查询,搜索引擎的自动补全甚至 IP 路由。
下图展示了“top”, “thus”和“their”三个单词在前缀树中如何存储的:
在这里插入图片描述

哈希表

哈希(Hash)将某个对象变换为唯一标识符,该标识符通常用一个短的随机字母和数字组成的字符串来代表。哈希可以用来实现各种数据结构,其中最常用的就是哈希表(hash table)
哈希表的性能取决于 3 个指标:

  • 哈希函数
  • 哈希表的大小
  • 哈希冲突处理方式

下图展示了有数组实现的哈希表,数组的下标即为哈希值,由哈希函数计算,作为哈希表的键(key),而数组中保存的数据即为值(value):
在这里插入图片描述

常见的算法

深度优先遍历

深度优先遍历(Depth First Search)的主要思想是
1、首先以一个未被访问过的顶点作为起始顶点,沿当前顶点的边走到未访问过的顶点;
2、当没有未访问过的顶点时,则回到上一个顶点,继续试探别的顶点,直至所有的顶点都被访问过。
深度优先遍历是在图的数据结构中提出的概念,但是树是一种特殊的图,因此我们也可以将深度、广度优先遍历应用于数中,借助栈来实现相应的效果
使用二叉树原理分析:
在这里插入图片描述
深度优先搜索的步骤为:

(1)、首先节点 1 进栈,节点1在栈顶;
(2)、然后节点1出栈,访问节点1,节点1的孩子节点3进栈,节点2进栈;
(3)、节点2在栈顶,然后节点2出栈,访问节点2
(4)、节点2的孩子节点5进栈,节点4进栈
(5)、节点4在栈顶,节点4出栈,访问节点4,
(6)、节点4左右孩子为空,然后节点5在栈顶,节点5出栈,访问节点5;
(7)、节点5左右孩子为空,然后节点3在站顶,节点3出栈,访问节点3;
(8)、节点3的孩子节点7进栈,节点6进栈
(9)、节点6在栈顶,节点6出栈,访问节点6;
(10)、节点6的孩子为空,这个时候节点7在栈顶,节点7出栈,访问节点7
(11)、节点7的左右孩子为空,此时栈为空,遍历结束。
深度遍历代码演示
共用的二叉树节点代码:

/**
 * 二叉树数据结构
 */
public class TreeNode {
	int data;
	TreeNode leftNode;
	TreeNode rightNode;
	public TreeNode() {
	}
	public TreeNode(int d) {
		data=d;
	}
	
	public TreeNode(TreeNode left,TreeNode right,int d) {
		leftNode=left;
		rightNode=right;
		data=d;
	}
}
//深度优先遍历,借助栈的数据结构
public void depthFirstSearch(TreeNode nodeHead) {
		if(nodeHead==null) {
			return;
		}
		Stack<TreeNode> myStack=new Stack<>();
		myStack.add(nodeHead);
		while(!myStack.isEmpty()) {
			TreeNode node=myStack.pop();    //弹出栈顶元素
			System.out.print(node.data+" ");
			if(node.rightNode!=null) {
				myStack.push(node.rightNode);    //深度优先遍历,先遍历左边,后遍历右边,栈先进后出
			}
			if(node.leftNode!=null) {
				myStack.push(node.leftNode);
			}
		}
		
	}

结果:1 2 4 5 3 6 7

广度优先遍历

广度优先遍历(Depth First Search)的主要思想是:类似于树的层序遍历。
广度优先遍历是连通图的一种遍历策略,因为它的思想是从一个顶点V0开始,辐射状地优先遍历其周围较广的区域故得名。
根据广度优先遍历的特点我们利用Java数据结构队列Queue来实现
在这里插入图片描述

广度优先搜索的步骤为:
(1)、节点1进队,节点1出队,访问节点1
(2)、节点1的孩子节点2进队,节点3进队。
(3)、节点2出队,访问节点2,节点2的孩子节点4进队,节点5进队;
(4)、节点3出队,访问节点3,节点3的孩子节点6进队,节点7进队;
(5)、节点4出队,访问节点4,节点4没有孩子节点。
(6)、节点5出队,访问节点5,节点5没有孩子节点。
(7)、节点6出队,访问节点6,节点6没有孩子节点。
(8)、节点7出队,访问节点7,节点7没有孩子节点,结束。
广度优先遍历的代码表示

//广度优先遍历是使用队列实现的
	public void BroadFirstSearch(TreeNode nodeHead) {
		if(nodeHead==null) {
			return;
		}
		Queue<TreeNode> myQueue=new LinkedList<>();
		myQueue.add(nodeHead);
		while(!myQueue.isEmpty()) {
			TreeNode node=myQueue.poll(); //队列出队
			System.out.print(node.data+" ");
			if(null!=node.leftNode) {
				myQueue.add(node.leftNode);    //深度优先遍历,我们在这里采用每一行从左到右遍历
			}
			if(null!=node.rightNode) {
				myQueue.add(node.rightNode);
			}
			
		}
	}

结果:1 2 3 4 5 6 7

查找第一个没有重复的数组元素

直接上代码,查找第一个没有重复的数据,通过双重循环,当查找到第一个之后,直接跳出内部循环,然后return将方法结果返回。

  /**
     * 查找数组内第一个不重复的数据
     * @param arr
     * @param n
     * @return
     */
    static int firstNonRepeating(int arr[], int n)
    {
        for (int i = 0; i < n; i++) {
            int j;
            for (j = 0; j < n; j++)
                if (i != j && arr[i] == arr[j])
                    break;
            //证明内循环整个遍历完成,没有找到相同数据,因此返回当前位置的数据        
            if (j == n)
                return arr[i];
        }

        return -1;
    }

若是查找第一个重复了2次的数组元素,则可以借助 LinkedHashMap 实现,将map的key存为元素的值,map的value记为元素出现的次数,然后利用 LinkedHashMap 的有序性,iterator 遍历,获取到第一个重复2次的数组元素。

返回链表倒数第 N 个元素

**简单粗暴直接思路:**遍历两次,第一次遍历整个列表确定长度n,而倒数第k个即第n-k-1(注意起点为0)个,第二次遍历走n-k-1步即可获得答案。缺点:节点数量较多时,节点从硬盘到物理内存的读写是一个耗时操作。
**巧妙的方法:**定义两个指针,我们都知道倒数第k个距离最后一个的距离是k-1,所以可以先移动一个指针走k步后,然后两个指针同时移动,那么在快的指针到达结尾时,慢的指针到达的位置正好是倒数第k个。

//如何找出单链表中的倒数第k个元素
 
class Node{
  Node next=null;
  int data;
  public Node(int data){
    this.data=data;
  }
}
 
public class findKelem {
  public static Node method(Node head,int k) {
    if(k<0) {
      return null;
    }
    Node p1=head;
    Node p2=head;
    for(int i=0;i<k&&p1!=null;++i) { //此处要注意数组越界
      p1=p1.next;
    }

    while(p1!=null) {
      p1=p1.next;
      p2=p2.next;
    }
    if(p2 == null)
    	System.out.println(head.data);
    return p2;
  }
}

计算树的高度的节点数

采用递归的方法计算。
树的高度:

public int getHeight(TreeNode node){
	if(node == null){
		return 0;
	}
	int i = getHeight(node.left);
	int j = getHeight(node.right);
	return (i<=j)? j+1:i+1;
}

树的节点数:

public int getSize(TreeNode node){
	if(node == null){
	return 0;
	}
	return 1+getSize(node.left)+getSize(node.right);
}

查找二叉平衡树中第 K 小的元素

思路:利用平衡二叉树所有左节点比当前节点小,右节点比当前节点大的原理,结合栈数据结构完成。
按照顺序将左节点入栈,并判断是否达到k,若没有,则从最小的节点开始,取右节点,在将该右节点的所有左节点入栈判断。

public int kthSmallest(TreeNode root, int k) {
     Stack<TreeNode> stack = new Stack<>();
     while(root != null || !stack.isEmpty()) {
         while(root != null) {
             stack.push(root);    
             root = root.left;   
         } 
         root = stack.pop();
         if(--k == 0) break;
         root = root.right;
     }
     return root.val;
 }

查找树中与根节点距离为 k 的节点

可以使用递归的方法来完成。时间复杂度是:O(n),在这里n是已知二叉树中节点的数目。

/* A binary tree node has data, pointer to left child
   and a pointer to right child */
class Node{
    int data;
    Node left, right;

    Node(int item){
        data = item;
        left = right = null;
    }
}

class BinaryTree{
    Node root;

    void printKDistant(Node node, int k){
        if (node == null)
            return;
        if (k == 0){
            System.out.print(node.data + " ");
            return;
        } 
        else{
            printKDistant(node.left, k - 1);
            printKDistant(node.right, k - 1);
        }
    }
}

冒泡排序

  • 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
  • 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的- 元素应该会是最大的数。
  • 针对所有的元素重复以上的步骤,除了最后一个。
  • 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

在这里插入图片描述
代码如下:

public class demo_sort {
    public static void main(String[] args) {
        //冒泡排序算法
        int[] numbers=new int[]{1,5,8,2,3,9,4};
        int i,j;
        for(i=0;i<numbers.length-1;i++)
        {
            for(j=0;j<numbers.length-1-i;j++) // 每次内层循环要少循环一个
            {
                if(numbers[j]>numbers[j+1])
                {
                    int temp=numbers[j];
                    numbers[j]=numbers[j+1];
                    numbers[j+1]=temp;
                }
            }
        }
        System.out.println("从小到大排序后的结果是:");
        for(i=0;i<numbers.length;i++)
            System.out.print(numbers[i]+" ");
    }
}

快速排序

假设我们现在对“6 1 2 7 9 3 4 5 10 8”这个10个数进行排序。首先在这个序列中随便找一个数作为基准数(一般取最前面的一个数)。为了方便,就让第一个数6作为基准数吧。接下来,需要将这个序列中所有比基准数大的数放在6的右边,比基准数小的数放在6的左边
过程拆解

  • 比较时,要让右边的先动,往左一定,碰见比基准小的数停止;然后让左边的往右移动,碰见比基准数大的停止,然后交换上面的两个数据。
  • 排序结束的条件是右边和左边的两个数移动到同一个位置,也就是二者相遇的时候。
  • 当二者相遇的时候,将相遇的点和基准点交互位置。
  • 当完成第一轮的排序后,在分别递归对刚刚排好序的左边和右边分别排序。
public class QuickSort {

#arr 需要排序的数组
#low 开始时最左边的索引=0
#high 开始时最右边的索引=arr.length-1
    public static void quickSort(int[] arr,int low,int high){
        int i,j,temp,t;
        if(low>high){
            return;
        }
        i=low;#左边哨兵的索引
        j=high;#右边哨兵的索引
        //temp就是基准位
        temp = arr[low];#以最左边为  基准位

        while (i<j) {
            //先看右边,依次往左递减
            #先从右往左找一个小于 基准位的数
            #当右边的哨兵位置所在的数>基准位的数 时
            #继续从右往左找(同时 j 索引-1)
            #找到后会跳出 while循环
            while (temp<=arr[j]&&i<j) {
                j--;
            }

            //再看左边,依次往右递增
            #步骤和上面类似
            while (temp>=arr[i]&&i<j) {
                i++;
            }

            //如果满足条件则交换
            if (i<j) {
#z、y 都是临时参数,用于存放 左右哨兵 所在位置的数据
                 z = arr[i];
                 y = arr[j];

                 # 左右哨兵 交换数据(互相持有对方的数据)
                 arr[i] = y;
                 arr[j] = z;
            }
        }

#这时 跳出了 “while (i<j) {}” 循环
#说明 i=j 左右在同一位置
        //最后将基准为与i和j相等位置的数字交换
         arr[low] = arr[i];#或 arr[low] = arr[j];
         arr[i] = temp;#或 arr[j] = temp;


#i=j
#这时  左半数组<(i或j所在索引的数)<右半数组
#也就是说(i或j所在索引的数)已经确定排序位置, 所以就不用再排序了,
# 只要用相同的方法 分别处理  左右数组就可以了

        //递归调用左半数组
        quickSort(arr, low, j-1);
        //递归调用右半数组
        quickSort(arr, j+1, high);
    }


    public static void main(String[] args){
        int[] arr = {10,7,2,4,7,62,3,4,2,1,8,9,19};
        quickSort(arr, 0, arr.length-1);
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }
    }
}

插入排序

插入排序与打扑克时整理手上的牌非常类似。摸来的第1张牌无须整理,此后每次从桌上的牌(无序区)中摸最上面的1张并插入左手的牌(有序区)中正确的位置上。为了找到这个正确的位置,须自左向右(或自右向左)将摸来的牌与左手中已有的牌逐一比较。
在这里插入图片描述
代码实现:


/*
 * 插入排序算法:
 * 1、以数组的某一位作为分隔位,比如index=1,假设左面的都是有序的.
 * 
 * 2、将index位的数据拿出来,放到临时变量里,这时index位置就空出来了.
 * 
 * 3、从leftindex=index-1开始将左面的数据与当前index位的数据(即temp)进行比较,如果array[leftindex]>temp,
 * 则将array[leftindex]后移一位,即array[leftindex+1]=array[leftindex],此时leftindex就空出来了.
 * 
 * 4、再用index-2(即leftindex=leftindex-1)位的数据和temp比,重复步骤3,
 * 直到找到<=temp的数据或者比到了最左面(说明temp最小),停止比较,将temp放在当前空的位置上.
 * 
 * 5、index向后挪1,即index=index+1,temp=array[index],重复步骤2-4,直到index=array.length,排序结束,
 * 此时数组中的数据即为从小到大的顺序.
 * 
 * @author bjh
 *
 */
public class InsertSort {
    /*
     * 插入排序方法
     */
    public void doInsertSort(){
        for(int index = 1; index<length; index++){//外层向右的index,即作为比较对象的数据的index
            int temp = array[index];//用作比较的数据
            int leftindex = index-1;
            while(leftindex>=0 && array[leftindex]>temp){//当比到最左边或者遇到比temp小的数据时,结束循环
                array[leftindex+1] = array[leftindex]; //将比临时temp值大的往后移一位
                leftindex--; //修改标识符,同时给空位正确赋值index
            }
            array[leftindex+1] = temp;//把temp放到空位上
        }
    }
}

堆排序

最大堆:根结点的键值是所有堆结点键值中最大者,且每个结点的值都比其孩子的值大
最小堆:根结点的键值是所有堆结点键值中最小者,且每个结点的值都比其孩子的值小

堆排序算法:

  • 1、构造初始堆,从最后一个非叶节点开始调整。
    选出叶子节点中比自己大的一个交换,如果交换后的叶子节点不满足堆,则继续调整。
  • 2、构造好初始堆之后,将堆头元素交换到堆尾,堆尾的元素就已经是有序的了,然后一直重复,直到所有都有序。
/**
 * @author: gethin
 * @create: 2018-05-23 16:21
 * @description: 常用排序算法
 **/
public class Sort {
    public static void main(String[] args) {
        int[] nums = {16,7,3,20,17,8};
        headSort(nums);
        for (int num : nums) {
            System.out.print(num + " ");
        }
    }

    /**
     * 堆排序
     */
    public static void headSort(int[] list) {
        //构造初始堆,从第一个非叶子节点开始调整,左右孩子节点中较大的交换到父节点中
        for (int i = (list.length) / 2 - 1; i >= 0; i--) {
            headAdjust(list, list.length, i);
        }
        //排序,将最大的节点放在堆尾,然后从根节点重新调整
        for (int i = list.length - 1; i >= 1; i--) {
            int temp = list[0];
            list[0] = list[i];
            list[i] = temp;
            headAdjust(list, i, 0);
        }
    }
    
    private static void headAdjust(int[] list, int len, int i) {
        int k = i, temp = list[i], index = 2 * k + 1;
        while (index < len) {
            if (index + 1 < len) {
                if (list[index] < list[index + 1]) {
                    index = index + 1;
                }
            }
            if (list[index] > temp) {
                list[k] = list[index];
                k = index;
                index = 2 * k + 1;
            } else {
                break;
            }
        }
        list[k] = temp;
    }
}

二分查找法

二分查找法默认数组或者集合中的数据是按照顺序排列的。

  • 循环法、
/*
 * 循环实现二分查找算法arr 已排好序的数组x 需要查找的数-1 无法查到数据
 */
public static int binarySearch(int[] arr, int x) {
    int low = 0;
    int high = arr.length-1;
    while(low <= high) {
        int middle = (low + high)/2;
        if(x == arr[middle]) {
            return middle;
        }else if(x <arr[middle]) {
            high = middle - 1;
        }else {
            low = middle + 1;
        }
    }
    return -1;
}
  • 递归法
/递归实现二分查找
public static int binarySearch(int[] dataset,int data,int beginIndex,int endIndex){
    int midIndex = (beginIndex+endIndex)/2;
    if(data <dataset[beginIndex]||data>dataset[endIndex]||beginIndex>endIndex){
        return -1;
    }
    if(data <dataset[midIndex]){
        return binarySearch(dataset,data,beginIndex,midIndex-1);
    }else if(data>dataset[midIndex]){
        return binarySearch(dataset,data,midIndex+1,endIndex);
    }else {
        return midIndex;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值