1线性表基础
1.1线性表
线性表使具有相同特征数据元素的一个有限序列,其中所含元素的个数是线性表的长度。
从语言实现的角度
顺序表有两种基本实现方式,一体式和分离式。
一体式结构,存储表信息的单元与元素存储区以连续的方式安排在一块存储区里,两部分数据的整体形成一个完整的顺序表对象。
分离式结构,表对象里只保存与整个表有关的信息(即容器和元素个数),实际数据元素存放在另一个独立的元素存储区里,通过链接与基本表对象关联。
从存储的角度
分为顺序型和链表型。
顺序型是将数据存放在固定的区间内,此时访问元素的效率很高,但删除和增加元素代价较大,如果要扩容只能整体搬迁。
链表型是元素之间是通过地址依次连结的,因此访问时必须从头开始逐步向后找,访问效率低,而删除和增加元素很方便,不需考虑扩容问题。
从访问限制的角度
栈和队列称为访问受限的线性表,插入和删除受到限制,只能在固定的位置进行。
从扩容的角度
链表、树、图扩容最灵活。数组最受限制。
1.2数组的概念
数组时元素一个紧密在一起的序列,相互之间不需要记录彼此关系就能访问。
数组用索引的数字来表示每一项数据在数组中的位置,且在大多数编程语言中,索引是从0算起的。
2数组的基本操作
2.1数组创建和初始化
int[] arr=new int[10];
int[] arr={2,5,0,4,6};
2.2查找一个元素
/**
* @param size 已经存放的元素个数
* @param key 待查找的元素
*/
public static int findByElement(int[] arr,int size,int key){
for(int i=0;i<size;i++){
if(arr[i]==key)
return i;
}
return -1;
}
2.3增加一个元素
将给定的元素插入到有序数组的对应位置中,可以先找位置,再将其后元素整体右移,最后插入到空位置上。
- 判断数组是否已满,如果满了直接返回-1
- 初始化index为要插入的位置size
- 从头遍历数组,如果element小于当前元素,则更新index为当前索引i
- 元素后移,使空出index位置
- j从size开始向index遍历
- 每轮将j位置的元素复制到j-1位置
- 当遍历结束后,index位置空出
- 在index位置插入新元素
- 返回index
/**
* @param arr
* @param size 数组已经存储的元素数量,从1开始编号
* @param element 待插入的元素
* @return
*/
public static int addByElementSequence(int[] arr,int size,int element){
//size是从1开始编号,当size>=arr.length时,表示数组已满,不能再往里面添加元素
if(size>=arr.length)
return -1;
//问题1:这里index=0或index=size-1?index需要从size开始匹配找到要插入的位置
int index=size;
//问题2:这里i<size-1可以吗?i<size,要遍历到size位置,以便将element插入到最后
for(int i=0;i<size;i++){
if(element<arr[i]){
index=i;
break;
}
}
//元素后移,问题3:这里j=size-1可以吗?从size开始,确保参与后移的元素范围是从index到size
for(int j=size;j>index;j--){
arr[j]=arr[j-1];//index下标开始的元素后移一个位置
}
arr[index]=element;//插入元素
return index;
}
2.4删除一个元素
先查是否存在元素,存在再删除元素
- 遍历数组,找到要删除的元素的索引index
- 如果找到,则从index+1位置开始遍历
- 每轮将当前元素arr[i]复制到i-1位置,实现元素向前移动
- 循环结束后,最后一个元素被覆盖,所以size-1
- 如果没找到元素,index为-1,size不变
- 返回新size
/**
* 遍历数组,如果发现目标元素,则将其删除,
* 数组的删除就是从目标元素开始,用后续元素依次覆盖前继元素
*
* @param arr 数组
* @param size 数组中的元素个数
* @param key 要删除的目标值
*/
public int removeByElement(int[] arr,int size,int key){
int index=-1;
for(int i=0;i<size;i++){
if(arr[i]==key){
index=i;
break;
}
}
if(index!=-1){
for(int i=index+1;i<size;i++)
arr[i-1]=arr[i];
size--;
}
return size;
}
3单调数组问题
- 调用isSorted方法两次,一次判断递增排序,一次判断递减排序
- 在isSorted方法中:
- 通过increasing参数控制递增还是递减校验
- 循环比较相邻元素
- 若出现违反排序规则,返回false
- 若遍历完毕都没出现,返回true
- 若数组满足递增或递减排序中的任意一种,是单调数组
public boolean isMonotonic(int[] nums){
return isSorted(nums,true)||isSorted(nums,false);
}
public boolean isSorted(int[] nums,boolean increasing){
int n=nums.length;
for(int i=0;i<n-1;++i){
if(increasing){
if(nums[i]>nums[i+1]){
return false;
}
}else{
if(nums[i]<nums[i+1]){
return false;
}
}
}
return true;
}
直接遍历数组,判断相邻元素是递增还是递减的。
public boolean isMonotonic2(int[] nums){
boolean inc=true,dec=true;
int n=nums.length;
for(int i=0;i<n-1;++i){
if(nums[i]>nums[i+1]){
inc=false;
}
if(nums[i]<nums[i+1]){
dec=false;
}
}
return inc||dec;
}
- 二分查找,初始化左右边界和结果ans
- 当左右边界不越界时循环
- 计算mid中间索引
- 若target<=mid处元素,说明应该在mid位置或左侧,所以右边界变为mid-1,变更新结果ans
- 如果target>mid处元素,说明应该在mid右侧,所以左边界变为mid
- 循环结束后,左边界右边界越界,此时ans是应该插入的位置
- 返回ans
public int searchInsert(int[] nums,int target){
int n=nums.length;
int left=0,right=n-1,ans=n;
while(left<=right){
int mid=((right-left)>>1)+left;
if(target<=nums[mid]){
ans=mid;
right=mid-1;
}else{
left=mid+1;
}
}
return ans;
}
4数组合并专题
4.1合并两个数组
public void merge(int[] nums1,int nums1_len,int[] nums2,int nums2_len){
int i=nums1_len+nums2_len-1;
int len1=nums1_len-1,len2=nums2_len-1;
while(len1>=0&&len2>=0){
if(nums1[len1]<=nums2[len2])
nums1[i--]=nums2[len2--];
else if(nums1[len1]>nums2[len2])
nums1[i--]=nums1[len1--];
}
//假如A或B数组还有剩余
while(len2!=-1)
num1[i--]=num2[len2--];
while(len1!=-1)
num1[i--]=num2[len1--];
}
4.2合并n个数组
public int[] mergeArrays(int[][] array){
int arrNums=array.length,arrLen;
if(arrNums==0){
return new int[0];
}
//数组长度校验
arrLen=array[0].length;
for(int i=1;i<arrNums;i++){
if(arrLen!=array[i].length){
return new int[0];
}
}
//开辟新空间
int[] result=new int[arrNums*arrLen];
//将各个数组依次合并到一起
for(int i=0;i<arrNums;i++){
for(int j=0;j<arrLen;j++){
result[i*srrLen+j]=array[i][j];
}
}
Arrays.sort(result);
return result;
}