1. 数据结构与算法定义
a. 数据结构
可以容纳数据的结构被称为数据结构,是静态的
b. 算法
用来对数据结构进行处理的方法,是动态的
2. 线性数据结构(一维数据结构)
a. 特点
线性的数据结构强调存储与顺序
b. 应用
数组和链表
3. 数组
a. 特性
- 存储在物理空间上是连续的
- 底层长度是不可变的
- 数组的变量,指向了数组第一个元素的位置
b. 优缺点
优点:
- 查询性能好,指定查询某个位置
- 一般的语言都是内置数组的,可以直接用
缺点:
- 因为空间必须是连续的,所以如果数组比较大,当系统的空间碎片较多的时候,容易存不下
- 因为数组的长度是固定的,所以数组的内容难以被添加和删除
c. 注意
- 如果数组 a = [1,2,3,4,5,6,7,8];这里的a其实是指向了数组第一个元素的位置,而a[1],a[2],a[3] 中的方括号表示存储地址的偏移。这属于操作系统的小知识,通过偏移来寻数据的性能最好。
- 数组的长度是不可变的,是从底层逻辑出发的,平时的数组长度可以通过push或者其他的方式在更改是因为JavaScript的v8引擎进行的操作。当你使用的长度超过数组的长度时,js会自动帮你进行一个数组扩容的操作,这里的数组扩容操作并不是在原本的存储空间后增加1个位置或者增加你缺少个数的存储空间,而是如下图所示。在另一个存储空间划分出一块新的空间,这块空间的大小并不是你刚才所需要的大小而是更大,为了减少你的扩容次数。如下图,本来是8个空间的数组,扩容之后在另一个空间划分出16个空间位置来给你存储(16为举例,为了表示会划分出比你需要的空间更大的空间),划分之后会把之前的数组复制过去,会消耗额外性能(cup)。所以在初始化数组的时候,如果数组的长度会变化,最好在初始化时考虑一下数据的理论范围之后对数组的大小进行赋值,以便减少不必要的性能损耗。
以上部分解释的是数组增加也就是数组扩容,当数组减少(删除)其中的元素的时候,如下图所示。当删除掉一个元素4(数组第3位)的时候,后面的位置会依次往前移动一位,也造成了不小的性能损耗。所以数组也尽量不要进行删除操作。
- 如果空间足够但是无法存下数组,也就是系统的空间碎片太多的时候,会触发系统的碎片整理,也是十分的消耗性能
d. 数组的遍历
var arr1 = [1,2,3,4,5]
// for循环遍历
for(var i = 0 ; i < arr1.lenght ; i++){
console.log(i)
}
// 输出
// 1
// 2
// 3
// 4
// 5
// foreach循环遍历
arr1.foreach(ele => {
console.log(ele)
})
// 输出
// 1
// 2
// 3
// 4
// 5
e. 数组逆序
var arr1 = [1, 2, 3, 4, 5]
arr1.reverse()
console.log(arr1); // [5,4,3,2,1]
f. 数组排序
ⅰ. 冒泡排序
//排序的本质: 排序不是比较大小,是比较和交换
var arr1 = [2, 3, 7, 5, 1, 8, 6, 9, 0, 4]
// 分方法
function compare(a, b) {//比较之后需要得出是否需要交换
if (a > b) return true;
else return false
}
function exchange(arr, a, b) {//将数组中ab位置里的指进行交换
var temp = arr[a]
arr[a] = arr[b]
arr[b] = temp
}
function sort(arr) {
if (arr === null) return;
for (var i = 0; i < arr.length; i++) {
for (var j = 0; j < arr.length - 1 - i; j++) {
if (compare(arr[j], arr[j + 1])) {
exchange(arr, j, j + 1)
}
}
}
}
// 一体
function bubbleSort(arr) {
if (arr === null) return;
for (var i = 0; i < arr.length; i++) {
for (var j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
let temp = arr[j + 1]
arr[j + 1] = arr[j]
arr[j] = temp
}
}
}
}
// bubbleSort(arr1)
sort(arr1)
console.log(arr1);
ⅱ. 选择排序
let arr1 = [2, 3, 7, 5, 1, 8, 6, 9, 0, 4]
function compare(a, b) {
if (a > b) return true
else return false
}
function exchange(arr, a, b) {
let temp = arr[a]
arr[a] = arr[b]
arr[b] = temp
}
// 选择排序,内层循环,每一圈选出一个最大的,然后放在后面
function sort(arr) {
if (arr === null) return;
for (let i = 0; i < arr.length; i++) {
let maxIndex = 0
for (let j = 0; j < arr.length - i; j++) {
if (compare(arr[maxIndex], arr[j])) {
maxIndex = j
}
}
exchange(arr, maxIndex, arr.length - 1 - i)
}
}
sort(arr1)
console.log(arr1)
ⅲ. 快速排序
let arr1 = [2, 3, 7, 5, 1, 8, 6, 9, 0, 4]
// 简单实现
function easyQuickSort(arr) {
if (arr === null || arr.length === 0) return [];
// 选队长
let leader = arr[0]
// 小的站左边,大的站右边
let left = []
let right = []
for (var i = 1; i < arr.length; i++) {
if (arr[i] < leader) left.push(arr[i]);
else right.push(arr[i]);
}
left = quickSort(left)
right = quickSort(right)
left.push(leader)
return left.concat(right)
}
// console.log(easyQuickSort(arr1))
// 标准实现
function compare(a, b) {
if (a > b) return true
else return false
}
function exchange(arr, a, b) {
let temp = arr[a]
arr[a] = arr[b]
arr[b] = temp
}
function sort(arr, begin, end) {
if (arr === null || arr.length === 0 || begin >= end - 1) return
let left = begin
let right = end
do {
do left++; while (left < right && compare(arr[begin], arr[left]))
do right--; while (right > left && compare(arr[right], arr[begin]))
if (left < right) exchange(arr, left, right)
} while (left < right);
var changePoint = left === right ? right - 1 : right
exchange(arr, begin, changePoint)
sort(arr, begin, changePoint)
sort(arr, changePoint + 1, end)
}
// 入口
function quickSort(arr) {
sort(arr, 0, arr.length)
}
quickSort(arr1)
console.log(arr1)
4. 链表
a. 特性
- 储存在物理空间上可以不是连续的
- 每存放一个值,都要多开销一个引用空间
b. 优缺点
- 优点
- 只要内存足够大,就能存的下。不用担心空间碎片的问题
- 链表的添加和删除非常容易
- 缺点
- 查询速度慢(值的查询某个位置)
- 链表的每一个节点都需要创建一个指向next的引用,浪费一些空间
c. 注意
- 如果想传递一个链表必须传递链表的根节点
- 因为链表的每一个节点都只记录下个节点的引用,所以每一个节点都认为自己是根节点
- 提到链表一般指的是单链表,如果是双链表会标明双链表。双链表的应用场景比单链表少,它的功能单链表都能实现,并且双链表更消耗资源
- 当节点内数据越多的时候,next引用占用内存的占比就越少,多开销的内存影响就越少
- 链表如果要删除一个节点就是把这个节点的上个节点指向这个节点的下个节点,如下图所示。以1为根节点的链表中没有指向3了,允许3的客观上的存在但是已经与1为根节点的链表无关了,也就是通过改变了2次引用就实现了删除的操作,比数组的删除以及扩容更节省资源。
同理增加一个节点也是改变2次引用,如下图所示。
d. 链表的遍历
// 创建链表节点函数
function createNode(value){
this.value = value
this.next = null
}
// 创建5个节点
var node1 = createNode(1)
var node2 = createNode(2)
var node3 = createNode(3)
var node4 = createNode(4)
var node5 = createNode(5)
// 更改节点指向形成链表
node1.next = node2
node2.next = node3
node3.next = node4
node4.next = node5
node5.next = null
// 采用循环法遍历链表
function foreachNode(node){
var temp = node
while(true){
if(temp !== null){
console.log(temp)
}else{
break;
}
temp = temp.next
}
}
//采用递归法遍历链表
function foreachNode(node){
if(node === null) return;
console.log(node)
foreachNode(node.next)
}
foreachNode(node1)
e. 链表逆序
// 创建链表节点函数
function createNode(value){
this.value = value
this.next = null
}
// 创建5个节点
var node1 = createNode(1)
var node2 = createNode(2)
var node3 = createNode(3)
var node4 = createNode(4)
var node5 = createNode(5)
// 更改节点指向形成链表
node1.next = node2
node2.next = node3
node3.next = node4
node4.next = node5
node5.next = null
//用递归逆序链表
function reverseNode(node){
if(node.next.next === null){
node.next.next = node
}else{
reverseNode(node.next)
node.next.next = node
node.next = null
}
}
reverseNode(node1)
5. 双向链表
- 双向链表优点,无论给出哪一个节点,都能对整个链表进行遍历
- 双向链表的缺点,多耗费一个引用空间,而且构建双向链表比较复杂
function Node(value) {
this.value = value;
this.next = null;
this.pre = null;
}
var node1 = new Node(1)
var node2 = new Node(2)
var node3 = new Node(3)
var node4 = new Node(4)
var node5 = new Node(5)
node1.next = node2
node2.pre = node1
node2.next = node3
node3.pre = node2
node3.next = node4
node4.pre = node3
node4.next = node5
node5.pre = node4
最后
今天给大家介绍了什么是数据结构,以及2种线性数据结构的特点、遍历和逆序、排序的方法。后面将会为大家带来二维数据结构的相关内容~