赞
踩
官方解释:
数据结构是一门研究非数值计算的程序设计问题中的操作对象,以及他们之间的关系和操作等相关问题的学科。
大白话:
数据结构就是把数据元素按照一定的关系组织起来的集合,用来组织和存储数据
传统上,我们可以把数据结构分为逻辑结构和物理结构两大类
逻辑结构分类:
逻辑结构是从具体问题中抽象出来的模型,是抽象意义上的结构,按照对象中数据元素之间的相互关系分类。
物理结构分类:
逻辑结构在计算机中真正的表示方式(又称为映像)称为物理结构,也可以叫做存储结构。常见的物理结构有顺序存储结构、链式存储结构。
把数据元素放到地址连续的存储单元里面,其数据间的逻辑关系和物理关系是一致的 ,比如我们常用的数组就是顺序存储结构。
顺序存储结构存在一定的弊端,就像生活中排时也会有人插队也可能有人有特殊情况突然离开,这时候整个结构都处于变化中,此时就需要链式存储结构。
是把数据元素存放在任意的存储单元里面,这组存储单元可以是连续的也可以是不连续的。此时,数据元素之间并
不能反映元素间的逻辑关系,因此在链式存储结构中引进了一个指针存放数据元素的地址,这样通过地址就可以找
到相关联数据元素的位置
官方解释:
算法是指解题方案的准确而完整的描述,是一系列解决问题的清晰指令,算法代表着用系统的方法解决问题的策略
机制。也就是说,能够对一定规范的输入,在有限时间内获得所要求的输出。
大白话:
根据一定的条件,对一些数据进行计算,得到需要的结果。
算法函数中n最高次幂越小,算法效率越高
描述 | 增长的数量级 | 说明 | 举例 |
---|---|---|---|
常数级别 | 1 | 普通语句 | 两个数相加 |
对数级别 | logN | 二分策略 | 二分查找 |
线性级别 | N | 循环 | 找最大值 |
线性对数级别 | NlogN | 分治 | 归并排序 |
平方级别 | N^2 | 双层循环 | 检查所有元素对 |
复杂程度从低到高依次为:
O(1)<O(logn)<O(n)<O(nlogn)<O(n2)<O(n3)
空间复杂度:一个字节是8位
数据类型 | 内存占用字节数 |
---|---|
byte | 1 |
short | 2 |
int | 4 |
long | 8 |
float | 4 |
double | 8 |
boolean | 1 |
char | 2 |
例如: Date date = new Date(),则date这个变量需要占用8个字节来表示
常用排序算法分析
时间复杂度 | 空间复杂度 | ||
---|---|---|---|
类别 | 排序方法 | 平均情况 | 最好情况 |
插入类 | 插入排序 | O(N^2) O(N) | O(N^2) |
希尔排序 | O(N^1.3-2) | O(N) | O(N^2) |
选择类 | 选择排序 | O(N^2) | O(N^2) O(N^2) |
堆排序 | O(NlogN) | O(NlogN) | O(NlogN) |
交换类 | 冒泡排序 | O(N^2) O(N) | O(N^2) |
快速排序 | O(NlogN) | O(NlogN) | O(N^2) |
归并排序 | O(NlogN) | O(NlogN) | O(NlogN) |
基数排序 | O(d(r+n) | ) O(d(n+rd)) | O(d(r+n)) |
Java提供了一个接口Comparable就是用来定义排序规则的,在这里我们以案例的形式对Comparable接口做一个简单的回顾。
需求:
//学生类
public class Student implements Comparable<Student> {
private String username;
private int age;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"username='" + username + '\'' +
", age=" + age +
'}';
}
//定义比较规则
@Override
public int compareTo(Student o) {
return this.getAge()-o.getAge();
}
}
//测试类
public class StudentTest {
public static void main(String[] args) {
Student stu1 = new Student();
stu1.setUsername("zhangsan");
stu1.setAge(17);
Student stu2 = new Student();
stu2.setUsername("lisi");
stu2.setAge(19);
Comparable max = getMax(stu1, stu2);
System.out.println(max);
}
//测试方法,获取两个元素中的较大值
public static Comparable getMax(Comparable c1,Comparable c2){
int cmp = c1.compareTo(c2);
if (cmp>=0){
return c1;
}else{
return c2;
}
}
}
排序原理:
//冒泡排序
public void bubbleSort(int[] arr) {
for (int i = 0; i < arr.length - 1; i++) {
boolean flag = true;
for (int j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
flag = false;
}
}
if (flag) break;
}
}
排序原理:
//选择排序
public void selectSort(int[] arr) {
int index, min;
for (int i = 0; i < arr.length; i++) {
min = arr[i];
index = i;
for (int j = i + 1; j < arr.length; j++) {
if (min > arr[j]) {
min = arr[j];
index = j;
}
}
if (i != index) {
arr[index] = arr[i];
arr[i] = min;
}
}
}
排序原理:
//插入排序
public void insertSort(int[] arr) {
for (int i = 1; i < arr.length; i++) {
int val = arr[i], j = i - 1;
for (; j >= 0 && arr[j] > val; j--) {
arr[j + 1] = arr[j];
}
arr[j + 1] = val;
}
}
希尔排序是插入排序的一种,又称“缩小增量排序”,是插入排序算法的一种更高效的改进版本
排序原理:
//希尔排序
public void shellSort(int[] arr) {
for (int h= arr.length / 2; h> 0; h--) {
for (int i = h; i < arr.length; i++) {
int j = i, temp = arr[j];
if (temp < arr[j - h]) {
while (j - h>= 0 && temp < arr[j - gap]) {
arr[j] = arr[j - h];
j -= h;
}
arr[j] = temp;
}
}
}
}
定义:
定义方法时,在方法内部调用方法本身,称之为递归.
注意事项:
在递归中,不能无限制的调用自己,必须要有边界条件,能够让递归结束,因为每一次递归调用都会在栈内存开辟 新的空间,重新执行方法,如果递归的层级太深,很容易造成栈内存溢出。
//递归阶乘
public static int factorial(int n){
if (n==1){
return 1;
}
return n*factorial(n-1);
}
归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
排序原理:
public void mergeSort(int[] arr, int left, int right, int[] temp) {
if (left < right) {
int mid = left + (right - left) / 2;
mergeSort(arr, left, mid, temp);
mergeSort(arr, mid + 1, right, temp);
merge(arr, left, mid, right, temp);
}
}
public void merge(int[] arr, int left, int mid, int right, int[] temp) {
int i = left, j = mid + 1, t = 0, tempLeft;
while (i <= mid && j <= right) {
temp[t++] = arr[i] >= arr[j] ? arr[j++] : arr[i++];
}
while (i <= mid) {
temp[t++] = arr[i++];
}
while (j <= right) {
temp[t++] = arr[j++];
}
t = 0;
tempLeft = left;
while (tempLeft <= right) {
arr[tempLeft++] = temp[t++];
}
}
快速排序是对冒泡排序的一种改进。它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
排序原理:
切分原理:
把一个数组切分成两个子数组的基本思想:
public void quickSort(int[] arr, int left, int right) {
if (left >= right) return;
int i = left - 1, j = right + 1, mid = arr[(left + right) /2], temp;
while (i < j) {
do i++; while (i < j && arr[i] < mid);
do j--; while (i < j && arr[j] > mid);
if (i < j) {
temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
quickSort(arr, left, j);
quickSort(arr, j + 1, right);
}
线性表是最基本、最简单、也是最常用的一种数据结构。一个线性表是n个具有相同特性的数据元素的有限序列。
前驱元素: 若A元素在B元素的前面,则称A为B的前驱元素
后继元素: 若B元素在A元素的后面,则称B为A的后继元素
线性表的分类:
线性表中数据存储的方式可以是顺序存储,也可以是链式存储,按照数据的存储方式不同,可以把线性表分为顺序表和链表。
- 顺序表是在计算机内存中以数组的形式保存的线性表,线性表的顺序存储是指用一组地址连续的存储单元,依次存储线性表中的各个元素、使得线性表中再逻辑结构上响铃的数据元素存储在相邻的物理存储单元中,即通过数据元素物理存储的相邻关系来反映数据元素之间逻辑上的相邻关系。
- java中ArrayList集合的底层也是一种顺序表,使用数组实现,同样提供了增删改查以及扩容等功能。
get(i):不难看出,不论数据元素量N有多大,只需要一次eles[i]就可以获取到对应的元素,所以时间复杂度为O(1);
insert(int i,T t):每一次插入,都需要把i位置后面的元素移动一次,随着元素数量N的增大,移动的元素也越多,时间复杂为O(n);
remove(int i):每一次删除,都需要把i位置后面的元素移动一次,随着数据量N的增大,移动的元素也越多,时间复杂度为O(n);
链表是一种物理存储单元上非连续、非顺序的存储结构,其物理结构不能只管的表示数据元素的逻辑顺序,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列的结点(链表中的每一个元素称为结点)组成,结点可以在运行时动态生成。
单向链表是链表的一种,它由多个结点组成,每个结点都由一个数据域和一个指针域组成,数据域用来存储数据,指针域用来指向其后继结点。链表的头结点的数据域不存储数据,指针域指向第一个真正存储数据的结点。
双向链表也叫双向表,是链表的一种,它由多个结点组成,每个结点都由一个数据域和两个指针域组成,数据域用来存储数据,其中一个指针域用来指向其后继结点,另一个指针域用来指向前驱结点。链表的头结点的数据域不存储数据,指向前驱结点的指针域值为null,指向后继结点的指针域指向第一个真正存储数据的结点。
java中LinkedList集合也是使用双向链表实现,并提供了增删改查等相关方法
get(int i):每一次查询,都需要从链表的头部开始,依次向后查找,随着数据元素N的增多,比较的元素越多,时间复杂度为O(n)
insert(int i,T t):每一次插入,需要先找到i位置的前一个元素,然后完成插入操作,随着数据元素N的增多,查找的元素越多,时间复杂度为O(n);
remove(int i):每一次移除,需要先找到i位置的前一个元素,然后完成插入操作,随着数据元素N的增多,查找的元素越多,时间复杂度为O(n)
相比较顺序表,链表插入和删除的时间复杂度虽然一样,但仍然有很大的优势,因为链表的物理地址是不连续的,它不需要预先指定存储空间大小,或者在存储过程中涉及到扩容等操作,同时它并没有涉及的元素的交换。
相比较顺序表,链表的查询操作性能会比较低。因此,如果我们的程序中查询操作比较多,建议使用顺序表,增删操作比较多,建议使用链表。
快慢指针指的是定义两个指针,这两个指针的移动速度一块一慢,以此来制造出自己想要的差值,这个差值可以然我们找到链表上相应的结点。一般情况下,快指针的移动步长为慢指针的两倍
循环链表,顾名思义,链表整体要形成一个圆环状。在单向链表中,最后一个节点的指针为null,不指向任何结点,因为没有下一个元素了。要实现循环链表,我们只需要让单向链表的最后一个节点的指针指向头结点即可。
问题描述: 传说有这样一个故事,在罗马人占领乔塔帕特后,39 个犹太人与约瑟夫及他的朋友躲到一个洞中,39个犹太人决 定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,第一个人从1开始报数,依次往后,如果有人报数到3,那么这个人就必须自杀,然后再由他的下一个人重新从1开始报数,直到所有人都自杀身亡为止。然而约瑟夫和他的朋友并不想遵从。于是,约瑟夫要他的朋友先假装遵从,他将朋友与自己安排在第16个与 第31个位置,从而逃过了这场死亡游戏> 。
问题转换:
41个人坐一圈,第一个人编号为1,第二个人编号为2,第n个人编号为n。
- 编号为1的人开始从1报数,依次向后,报数为3的那个人退出圈;
- 自退出那个人开始的下一个人再次从1开始报数,以此类推;
- 求出最后退出的那个人的编号。
public class Test{
public static void main(String[] args) throws Exception {
//1.构建循环链表
Node<Integer> first = null;
//记录前一个结点
Node<Integer> pre = null;
for (int i = 1; i <= 41; i++) {
//第一个元素
if (i==1){
first = new Node(i,null);
pre = first;
continue;
}
Node<Integer> node = new Node<>(i,null);
pre.next = node;
pre = node;
if (i==41){
//构建循环链表,让最后一个结点指向第一个结点
pre.next=first;
}
}
//2.使用count,记录当前的报数值
int count=0;
//3.遍历链表,每循环一次,count++
Node<Integer> n = first;
Node<Integer> before = null;
while(n!=n.next){
//4.判断count的值,如果是3,则从链表中删除这个结点并打印结点的值,把count重置为0;
count++;
if (count==3){
//删除当前结点
before.next = n.next;
System.out.print(n.item+",");
count=0;
n = n.next;
}else{
before=n;
n = n.next;
}
}
/*打印剩余的最后那个人*/
System.out.println("最后"+n.item);
}
//结点类
private static class Node<T> {
//存储数据
T item;
//下一个结点
Node next;
public Node(T item,Node next) {
this.item = item;
this.next = next;
}
}
}
栈是一种基于先进后出(FILO)的数据结构,是一种只能在一端进行插入和删除操作的特殊线性表。它按照先进后出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据(最后一个数据被第一个读出来)。
我们称数据进入到栈的动作为压栈,数据从栈中出去的动作为弹栈。
public class Stack<T>{
private Node head; //记录首结点
private int N; //当前栈的元素个数
private class Node {
public T item;
public Node next;
public Node(T item, Node next) {
this.item = item;
this.next = next;
}
}
public Stack() {
this.head = new Node(null, null);
this.N = 0;
}
public boolean isEmpty() {//判断栈是否为空,是返回true,否返回false
return N == 0;
}
public int size() {//获取栈中元素的个数
return N;
}
public void push(T t) { //向栈中压入元素;//记录首结点
Node newNode = new Node(t, null);//创建压入的节点
Node next = head.next; //存储头节点原来的下个节点
head.next = newNode; //头节点指向新节点
newNode.next = next; //新节点指向next
N++;
}
public T pop() { //弹出栈顶元素
if (head.next == null) {
return null;
}
Node first = head.next;
head.next = first.next;
N--;
return first.item;
}
}
队列是一种基于先进先出(FIFO)的数据结构,是一种只能在一端进行插入,在另一端进行删除操作的特殊线性表,它按照先进先出的原则存储数据,先进入的数据,在读取数据时先读被读出来。
public class Queue<T>{
private Node head; //记录首结点
private int N; //当前栈的元素个数
private Node last; //记录最后一个结点
private class Node {
public T item;
public Node next;
public Node(T item, Node next) {
this.item = item;
this.next = next;
}
}
public Queue() {
this.head = new Node(null, null);
this.last = null;
this.N = 0;
}
public boolean isEmpty() { //判断队列是否为空,是返回true,否返回false
return N == 0;
}
public int size() { //获取队列中元素的个数
return N;
}
public void add(T t) { //往队列中插入一个元素
Node node = new Node(t, null);
if (last == null) {
last = node;
head.next = last;
} else {
Node oldLast = last;
last = node;
oldLast.next = last;
}
N++;
}
public T pop() { //从队列中拿出一个元素
if (isEmpty()) {
return null;
}
Node next = head.next;
head.next = next.next;
N--;
if (isEmpty()) {
last = null;
}
return next.item;
}
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。