一、数组
1.数组定义的关键词:“线性表”、“连续的内存空间”、“相同类型的数据”。
2.数组的重要特性:随机访问。根据下标访问元素时的时间复杂度为O(1)。
3.对于有序数组,在k处插入一个元素时,需要将k~n的元素向后进行迁移,此时最好的情况时间复杂度为O(1),最后的情况时间复杂度为O(n)。但对于数组内容无需有序时,可将k处的元素移动至数组末尾,这样插入的时间复杂度就为O(1)。
4.对于有序数组,在k处删除一个元素时,需要将k~n的元素想前进行迁移。此时最好的情况时间复杂度为O(1),最后的情况时间复杂度为O(n)。但在特殊情况,可以将数据标记为已删除,在没有更多存储空间时,再进行一次真正的删除,减少数据迁移次数。
5.在C语言中,数组越界有可能不会出现程序报错。因为访问数组的本质是访问一段连续的内存,只要通过偏移计算得到的内存地址是可用的,即使数组访问越界也可能不会出现报错。
6.一维数组的寻址公式为:a[k]_address=base_address+k*type_size。“下标”确切的定义为“偏移”,a[0]就是相对首地址偏移为0的内存地址,a[k]就是相对首地址偏移k个type_size的内存地址。
7.对于二维数组,在C/C++中,数据是先按行,再按列的方式连续存储的。例如,arr[3][2]
存储方式为arr[0][0]-arr[0][1]-arr[1][0]-arr[1][1]-arr[2][0]-arr[2][1]。其寻址公式为arr[i][j]_address=base_address+(i*n+j)*type_size(数组arr[m][n])。
8.C++代码
#include<iostream>
using namespace std;
int main()
{
int arr1[5] = { 99 };
int arr2[3][2] = { {1,5},{6,4},{7,2} };
long long base_address_arr1 = (long long)arr1;
long long base_address_arr2 = (long long)arr2;
cout << "arr1首地址为:" << (long long)arr1 << endl;
cout << "arr1[0]地址为:" << (long long)&arr1[0] << endl;
cout << "arr1[3]地址为:" << (long long)&arr1[3] << endl;
cout << "通过寻址公式得到arr1[3]地址为:" << base_address_arr1 + 3 * sizeof(int) << endl;
cout << endl;
cout << "arr2首地址为:" << (long long)arr2 << endl;
cout << "arr2[0][0]地址为:" << (long long)&arr2[0][0] << endl;
cout << "arr2[2][1]地址为:" << (long long)&arr2[2][1] << endl;
cout << "通过寻址公式得到arr2[2][1]地址为:" << base_address_arr2 + (2*2+1) * sizeof(int) << endl;
return 0;
}
二、链表
1.链表并不需要一块连续的内存空间,它通过“指针”将一组零散的内存块串联起来使用。链表更擅长插入、删除操作。
2.C++双向链表定义代码
struct Node
{
Node* previous;
int data;
Node* next;
};
struct List
{
Node* head;
Node* tail;
int size;
};
3.C++双向链表初始化代码
void initList(List* list)
{
list->head = NULL;
list->tail = NULL;
list->size = 0;
}
4.C++双向链表创建节点代码
Node* createNode(int data)
{
Node* newNode = new Node();
if (newNode == NULL)
{
cout << "内存分配失败!" << endl;
exit(EXIT_FAILURE);
}
newNode->data = data;
newNode->next = NULL;
newNode->previous = NULL;
return newNode;
}
5.C++双向链表添加节点代码
void addNode(List* list, int data)
{
Node* newNode = createNode(data);
if (list->head == NULL) //第一次添加
{
list->head = newNode;
list->tail = newNode;
list->size++;
return;
}
list->tail->next = newNode;
newNode->previous = list->tail;
list->tail = newNode;
list->size++;
}
6.C++双向链表插入节点代码
void insertNode(List* list, int data, int position)
{
if (position < 0) //插入位置非法
{
cout << "插入位置不正确!" << endl;
exit(EXIT_FAILURE);
}
Node* newNode = createNode(data);
if (list->head == NULL) //第一次插入
{
list->head = newNode;
list->tail = newNode;
list->size++;
return;
}
if (position == 0) //在头部插入
{
newNode->next = list->head;
list->head->previous = newNode;
list->head = newNode;
list->size++;
}
else
{
Node* temp = list->head;
int num = 1;
while (num != position && temp != NULL) //在链表找到插入位置
{
temp = temp->next;
num++;
}
if (temp == NULL) //插入位置非法
{
cout << "插入位置不正确!" << endl;
exit(EXIT_FAILURE);
}
newNode->next = temp->next;
temp->next->previous = newNode;
temp->next = newNode;
newNode->previous = temp;
list->size++;
}
}
7.C++双向链表查找元素代码
int searchNode(List* list, int target)
{
Node* temp = list->head;
int position = 1;
while (temp->data != target && temp != NULL)
{
temp = temp->next;
position++;
}
if (temp == NULL) //未找到
{
cout << "链表中无法查找到该元素!" << endl;
return -1;
}
else
{
return position;
}
}
8.C++双向链表删除节点代码
void deleteNode(List* list, int position)
{
if (position < 0) //删除位置非法
{
cout << "插入位置不正确!" << endl;
exit(EXIT_FAILURE);
}
if (list->size <= 0) //链表中无节点
{
cout << "链表为空!" << endl;
exit(EXIT_FAILURE);
}
if (position == 1) //删除头节点
{
list->head->next->previous = NULL;
list->head = list->head->next;
list->size--;
}
else
{
Node* temp = list->head;
int num = 1;
while (temp != NULL && position != num) //定位到要删除的位置
{
temp = temp->next;
num++;
}
if (temp == NULL) //删除位置非法
{
cout << "删除位置不存在!" << endl;
exit(EXIT_FAILURE);
}
if (temp->next = NULL) //删除最后一个节点
{
list->tail = temp;
temp->next = NULL;
list->size--;
}
else
{
temp->previous->next = temp->next;
temp->next->previous = temp->previous;
list->size--;
}
}
}
9.链表使用技巧:
(1)理解指针和引用的含义
(2)警惕指针丢失和内存泄漏
(3)利用“哨兵”简化代码
(4)留意边界问题和特殊情况
- 如果链表为空
- 如果链表只包含一个节点
- 如果链表只包含两个节点
- 如果要处理的是头节点,尾节点
(5)画图
(6)多练
三、栈
1.栈的特点是“先进后出”、“后进先出”。入栈就是在栈顶插入一个数据,出栈就是在栈顶删除一个数据。栈可分为顺序栈和链式栈。
2.栈在函数调用中的应用:在函数调用过程中,每调用一个新函数,编译器就会将调用函数的临时变量封装为栈帧并压入栈,当被调用函数执行完成并返回后,编译器就将这个函数对应的栈帧弹出栈。
四、队列
1.队列的特点是“先进先出”、“后进后出”。入队就是将数据放到队列尾部,出队就是将头部数据取出。队列可分为顺序队列和链式队列。