赞
踩
参考引用
// 存储在栈上
int arr[5];
int nums[5] { 1, 3, 2, 5, 4 };
// 存储在堆上(需要手动释放空间)
int* arr1 = new int[5];
int* nums1 = new int[5] { 1, 3, 2, 5, 4 };
/* 随机访问元素 */
int randomAccess(int *nums, int size) {
// 在区间 [0, size) 中随机抽取一个数字
int randomIndex = rand() % size;
// 获取并返回随机元素
int randomNum = nums[randomIndex];
return randomNum;
}
/* 在数组的索引 index 处插入元素 num */
void insert(int *nums, int size, int num, int index) {
// 把索引 index 以及之后的所有元素向后移动一位
for (int i = size - 1; i > index; i--) {
nums[i] = nums[i - 1];
}
// 将 num 赋给 index 处元素
nums[index] = num;
}
/* 删除索引 index 处元素 */
void remove(int *nums, int size, int index) {
// 把索引 index 之后的所有元素向前移动一位
for (int i = index; i < size - 1; i++) {
nums[i] = nums[i + 1];
}
}
数组的插入与删除操作有以下缺点,建议使用 vector
- 时间复杂度高:数组的插入和删除的平均时间复杂度均为 O(n),其中 n 为数组长度
- 丢失元素:由于数组的长度不可变,因此在插入元素后,超出数组长度范围的元素会丢失
- 内存浪费:可以初始化一个比较长的数组,只用前面一部分,这样在插入数据时,丢失的末尾元素都是 “无意义” 的,但这样做也会造成部分内存空间的浪费
void traverse(int *nums, int size) {
int count = 0;
// 通过索引遍历数组
for (int i = 0; i < size; i++) {
count++;
}
}
/* 在数组中查找指定元素 */
int find(int *nums, int size, int target) {
for (int i = 0; i < size; i++) {
if (nums[i] == target)
return i;
}
return -1;
}
优点
缺点
内存空间是所有程序的公共资源,在一个复杂系统运行环境下,空闲的内存空间可能散落在各处。存储数组的内存空间必须是连续的,而当数组非常大时,内存可能无法提供如此大的连续空间,此时链表的灵活性优势就被体现
链表的组成单位是节点(node)对象。每个节点都包含两项数据:节点的 “值” 和指向下一节点的指针
链表节点 ListNode 除了包含值,还需额外保存一个指针。因此在相同数据量下,链表比数组占用更多的内存空间
/* 链表节点结构体 */
struct ListNode {
int val; // 节点值
ListNode *next; // 指向下一节点的指针
ListNode(int x) : val(x), next(nullptr) {} // 构造函数
};
/* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */
// 初始化各个节点(通常将头节点当作链表的代称)
ListNode* n0 = new ListNode(1);
ListNode* n1 = new ListNode(3);
ListNode* n2 = new ListNode(2);
ListNode* n3 = new ListNode(5);
ListNode* n4 = new ListNode(4);
// 构建引用指向
n0->next = n1;
n1->next = n2;
n2->next = n3;
n3->next = n4;
数组整体是一个变量,比如数组 nums 包含元素 nums[0] 和 nums[1] 等,而链表是由多个独立的节点对象组成的
相比之下,在数组中插入元素的时间复杂度为 O(n),在大数据量下的效率较低
/* 在链表的节点 n0 之后插入节点 P */
void insert(ListNode *n0, ListNode *P) {
ListNode *n1 = n0->next;
P->next = n1;
n0->next = P;
}
/* 删除链表的节点 n0 之后的首个节点 */
void remove(ListNode *n0) {
if (n0->next == nullptr)
return;
// n0 -> P -> n1
ListNode *P = n0->next;
ListNode *n1 = P->next;
n0->next = n1;
// 释放内存
delete P;
}
/* 访问链表中索引为 index 的节点 */
ListNode *access(ListNode *head, int index) {
for (int i = 0; i < index; i++) {
if (head == nullptr)
return nullptr;
head = head->next;
}
return head;
}
/* 在链表中查找值为 target 的首个节点 */
int find(ListNode *head, int target) {
int index = 0;
while (head != nullptr) {
if (head->val == target)
return index;
head = head->next;
index++;
}
return -1;
}
数组长度不可变导致实用性降低。在实际中,可能事先无法确定需要存储多少数据,这使数组长度的选择变得困难。若长度过小,需要在持续添加数据时频繁扩容数组;若长度过大,则会造成内存空间的浪费
/* 1、初始化列表 */ // 无初始值 vector<int> list1; // 有初始值 vector<int> list = { 1, 3, 2, 5, 4 }; /* 2、访问元素 */ // 列表本质上是数组,因此可以在 O(1) 时间内访问和更新元素 int num = list[1]; // 访问索引 1 处的元素 /* 更新元素 */ list[1] = 0; // 将索引 1 处的元素更新为 0 /* 3、插入与删除元素 */ // 在列表尾部添加元素的时间复杂度为 O(1) // 但插入和删除元素的效率仍与数组相同,时间复杂度为 O(n) /* 清空列表 */ list.clear(); /* 尾部添加元素 */ list.push_back(1); list.push_back(3); list.push_back(2); list.push_back(5); list.push_back(4); /* 中间插入元素 */ list.insert(list.begin() + 3, 6); // 在索引 3 处插入数字 6 /* 删除元素 */ list.erase(list.begin() + 3); // 删除索引 3 处的元素 /* 4、通过索引遍历列表 */ int count = 0; for (int i = 0; i < list.size(); i++) { count++; } /* 直接遍历列表元素 */ count = 0; for (int n : list) { count++; } /* 5、拼接两个列表 */ vector<int> list1 = { 6, 8, 7, 10, 9 }; // 将列表 list1 拼接到 list 之后 list.insert(list.end(), list1.begin(), list1.end()); /* 6、排序列表 */ sort(list.begin(), list.end()); // 排序后,列表元素从小到大排列
/* 列表类简易实现 */ class MyList { private: int *nums; // 数组(存储列表元素) int numsCapacity = 10; // 列表容量 int numsSize = 0; // 列表长度(即当前元素数量) int extendRatio = 2; // 每次列表扩容的倍数 public: /* 构造方法 */ MyList() { nums = new int[numsCapacity]; } /* 析构方法 */ ~MyList() { delete[] nums; } /* 获取列表长度(即当前元素数量)*/ int size() { return numsSize; } /* 获取列表容量 */ int capacity() { return numsCapacity; } /* 访问元素 */ int get(int index) { // 索引如果越界则抛出异常,下同 if (index < 0 || index >= size()) throw out_of_range("索引越界"); return nums[index]; } /* 更新元素 */ void set(int index, int num) { if (index < 0 || index >= size()) throw out_of_range("索引越界"); nums[index] = num; } /* 尾部添加元素 */ void add(int num) { // 元素数量超出容量时,触发扩容机制 if (size() == capacity()) extendCapacity(); nums[size()] = num; // 更新元素数量 numsSize++; } /* 中间插入元素 */ void insert(int index, int num) { if (index < 0 || index >= size()) throw out_of_range("索引越界"); // 元素数量超出容量时,触发扩容机制 if (size() == capacity()) extendCapacity(); // 将索引 index 以及之后的元素都向后移动一位 for (int j = size() - 1; j >= index; j--) { nums[j + 1] = nums[j]; } nums[index] = num; // 更新元素数量 numsSize++; } /* 删除元素 */ int remove(int index) { if (index < 0 || index >= size()) throw out_of_range("索引越界"); int num = nums[index]; // 索引 i 之后的元素都向前移动一位 for (int j = index; j < size() - 1; j++) { nums[j] = nums[j + 1]; } // 更新元素数量 numsSize--; // 返回被删除元素 return num; } /* 列表扩容 */ void extendCapacity() { // 新建一个长度为原数组 extendRatio 倍的新数组 int newCapacity = capacity() * extendRatio; int *tmp = nums; nums = new int[newCapacity]; // 将原数组中的所有元素复制到新数组 for (int i = 0; i < size(); i++) { nums[i] = tmp[i]; } // 释放内存 delete[] tmp; numsCapacity = newCapacity; } /* 将列表转换为 Vector 用于打印 */ vector<int> toVector() { // 仅转换有效长度范围内的列表元素 vector<int> vec(size()); for (int i = 0; i < size(); i++) { vec[i] = nums[i]; } return vec; } };
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。