什么是循环数组
循环数组(Circular Array),也称为环形数组或环形缓冲区(Ring Buffer),是一种特殊的数组结构。它在逻辑上将数组的首尾相连,形成一个环形结构。当数组索引到达末尾时,会自动回到数组的开头,实现循环访问。
循环数组的核心思想是:通过取模运算,让索引在固定范围内循环。
为什么需要循环数组?
在传统的线性数组中,当我们需要在数组头部插入或删除元素时,需要移动大量元素,时间复杂度为 O(n)。而循环数组通过维护头尾指针,可以高效地在两端进行操作,实现 O(1) 的时间复杂度。
循环数组的核心概念
1. 基本结构
循环数组通常需要维护以下几个关键元素:
- data[]: 底层数组,用于存储实际数据
- front: 头指针,指向队列的第一个元素
- rear: 尾指针,指向队列最后一个元素的下一个位置
- capacity: 数组容量
- size: 当前元素个数
2. 索引计算
循环数组最关键的操作是索引的循环计算:
例如,在长度为 5 的数组中:
- 索引 0 的下一个位置是 (0 + 1) % 5 = 1
- 索引 4 的下一个位置是 (4 + 1) % 5 = 0(回到开头)
3. 判空与判满
判空条件:
1
| isEmpty = (size == 0) 或 (front == rear)
|
判满条件:
1
| isFull = (size == capacity) 或 ((rear + 1) % capacity == front)
|
注意:有些实现会预留一个空位来区分满和空的状态。
循环数组的实现
下面我们分别用 Swift、Objective-C 和 Python 实现一个完整的循环数组(以循环队列为例):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
| class CircularArray<T> { private var data: [T?] private var front: Int private var rear: Int private let capacity: Int private var size: Int init(capacity: Int) { self.capacity = capacity self.data = Array(repeating: nil, count: capacity) self.front = 0 self.rear = 0 self.size = 0 } func isEmpty() -> Bool { return size == 0 } func isFull() -> Bool { return size == capacity } func count() -> Int { return size } func enqueue(_ element: T) -> Bool { if isFull() { print("队列已满,无法添加元素") return false } data[rear] = element rear = (rear + 1) % capacity size += 1 return true } func dequeue() -> T? { if isEmpty() { print("队列为空,无法移除元素") return nil } let element = data[front] data[front] = nil front = (front + 1) % capacity size -= 1 return element } func peek() -> T? { if isEmpty() { return nil } return data[front] } func get(_ index: Int) -> T? { if index < 0 || index >= size { return nil } let actualIndex = (front + index) % capacity return data[actualIndex] } func printElements() { if isEmpty() { print("队列为空") return } var elements: [T] = [] for i in 0..<size { if let element = get(i) { elements.append(element) } } print("队列元素: \(elements)") } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
| @interface CircularArray<ObjectType> : NSObject
- (instancetype)initWithCapacity:(NSUInteger)capacity; - (BOOL)isEmpty; - (BOOL)isFull; - (NSUInteger)count; - (BOOL)enqueue:(ObjectType)element; - (ObjectType)dequeue; - (ObjectType)peek; - (ObjectType)getElementAtIndex:(NSUInteger)index; - (void)printElements;
@end
@implementation CircularArray { NSMutableArray *_data; NSUInteger _front; NSUInteger _rear; NSUInteger _capacity; NSUInteger _size; }
- (instancetype)initWithCapacity:(NSUInteger)capacity { self = [super init]; if (self) { _capacity = capacity; _data = [NSMutableArray arrayWithCapacity:capacity]; for (NSUInteger i = 0; i < capacity; i++) { [_data addObject:[NSNull null]]; } _front = 0; _rear = 0; _size = 0; } return self; }
- (BOOL)isEmpty { return _size == 0; }
- (BOOL)isFull { return _size == _capacity; }
- (NSUInteger)count { return _size; }
- (BOOL)enqueue:(id)element { if ([self isFull]) { NSLog(@"队列已满,无法添加元素"); return NO; } _data[_rear] = element; _rear = (_rear + 1) % _capacity; _size++; return YES; }
- (id)dequeue { if ([self isEmpty]) { NSLog(@"队列为空,无法移除元素"); return nil; } id element = _data[_front]; _data[_front] = [NSNull null]; _front = (_front + 1) % _capacity; _size--; return element; }
- (id)peek { if ([self isEmpty]) { return nil; } return _data[_front]; }
- (id)getElementAtIndex:(NSUInteger)index { if (index >= _size) { return nil; } NSUInteger actualIndex = (_front + index) % _capacity; return _data[actualIndex]; }
- (void)printElements { if ([self isEmpty]) { NSLog(@"队列为空"); return; } NSMutableArray *elements = [NSMutableArray array]; for (NSUInteger i = 0; i < _size; i++) { id element = [self getElementAtIndex:i]; if (element && element != [NSNull null]) { [elements addObject:element]; } } NSLog(@"队列元素: %@", elements); }
@end
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| class CircularArray: def __init__(self, capacity): """初始化循环数组""" self.capacity = capacity self.data = [None] * capacity self.front = 0 self.rear = 0 self.size = 0 def is_empty(self): """判断是否为空""" return self.size == 0 def is_full(self): """判断是否已满""" return self.size == self.capacity def count(self): """获取当前元素个数""" return self.size def enqueue(self, element): """在尾部添加元素""" if self.is_full(): print("队列已满,无法添加元素") return False self.data[self.rear] = element self.rear = (self.rear + 1) % self.capacity self.size += 1 return True def dequeue(self): """从头部移除元素""" if self.is_empty(): print("队列为空,无法移除元素") return None element = self.data[self.front] self.data[self.front] = None self.front = (self.front + 1) % self.capacity self.size -= 1 return element def peek(self): """查看队首元素(不移除)""" if self.is_empty(): return None return self.data[self.front] def get(self, index): """获取指定索引的元素""" if index < 0 or index >= self.size: return None actual_index = (self.front + index) % self.capacity return self.data[actual_index] def print_elements(self): """打印所有元素""" if self.is_empty(): print("队列为空") return elements = [] for i in range(self.size): element = self.get(i) if element is not None: elements.append(element) print(f"队列元素: {elements}")
|
使用示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| let circularArray = CircularArray<Int>(capacity: 5)
circularArray.enqueue(1) circularArray.enqueue(2) circularArray.enqueue(3) circularArray.enqueue(4) circularArray.enqueue(5)
circularArray.printElements()
circularArray.enqueue(6)
print(circularArray.dequeue()!) print(circularArray.dequeue()!)
circularArray.enqueue(6) circularArray.enqueue(7)
circularArray.printElements()
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| circular_array = CircularArray(5)
circular_array.enqueue(1) circular_array.enqueue(2) circular_array.enqueue(3) circular_array.enqueue(4) circular_array.enqueue(5)
circular_array.print_elements()
circular_array.enqueue(6)
print(circular_array.dequeue()) print(circular_array.dequeue())
circular_array.enqueue(6) circular_array.enqueue(7)
circular_array.print_elements()
|
循环数组的应用场景
1. 循环队列(Circular Queue)
这是循环数组最典型的应用。循环队列常用于:
- 操作系统的进程调度
- 网络数据包的缓冲
- 生产者-消费者模型
- 键盘输入缓冲区
2. 循环缓冲区(Ring Buffer)
在音视频处理、网络通信等场景中广泛使用:
- 音频播放器的缓冲区
- 视频解码器的帧缓冲
- 网络数据的接收缓冲
3. LRU 缓存
结合循环数组和哈希表可以实现高效的 LRU(Least Recently Used)缓存。
4. 滑动窗口问题
在算法题中,很多滑动窗口问题可以用循环数组来优化。
性能分析
时间复杂度
操作 |
时间复杂度 |
入队(enqueue) |
O(1) |
出队(dequeue) |
O(1) |
查看队首(peek) |
O(1) |
判空/判满 |
O(1) |
随机访问 |
O(1) |
空间复杂度
空间复杂度为 O(n),其中 n 是数组的容量。
优势
- 高效的两端操作:在头尾插入删除都是 O(1)
- 固定内存占用:预分配固定大小的内存,避免动态扩容
- 缓存友好:连续的内存空间,对 CPU 缓存友好
劣势
- 固定容量:容量固定,无法动态扩展(除非重新分配)
- 空间浪费:某些实现需要预留一个空位来区分满和空
循环数组 vs 普通数组
特性 |
循环数组 |
普通数组 |
头部插入 |
O(1) |
O(n) |
头部删除 |
O(1) |
O(n) |
尾部插入 |
O(1) |
O(1) |
尾部删除 |
O(1) |
O(1) |
随机访问 |
O(1) |
O(1) |
空间利用 |
固定容量,可能浪费 |
可动态扩容 |
常见陷阱与注意事项
1. 索引计算错误
在进行索引计算时,特别是向前移动时,需要注意负数的处理:
1 2 3 4 5
| front = (front - 1) % capacity
front = (front - 1 + capacity) % capacity
|
2. 满和空的判断
有两种常见的判断方法:
方法1:使用 size 变量
1 2
| isEmpty = (size == 0) isFull = (size == capacity)
|
方法2:预留一个空位
1 2
| isEmpty = (front == rear) isFull = ((rear + 1) % capacity == front)
|
3. 线程安全问题
在多线程环境下使用循环数组需要加锁:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| class ThreadSafeCircularArray<T> { private var circularArray: CircularArray<T> private let lock = NSLock() init(capacity: Int) { self.circularArray = CircularArray<T>(capacity: capacity) } func enqueue(_ element: T) -> Bool { lock.lock() defer { lock.unlock() } return circularArray.enqueue(element) } func dequeue() -> T? { lock.lock() defer { lock.unlock() } return circularArray.dequeue() } }
|
扩展:动态扩容的循环数组
虽然循环数组通常是固定容量的,但我们也可以实现动态扩容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| class DynamicCircularArray<T> { private var data: [T?] private var front: Int private var rear: Int private var capacity: Int private var size: Int init(capacity: Int = 10) { self.capacity = capacity self.data = Array(repeating: nil, count: capacity) self.front = 0 self.rear = 0 self.size = 0 } func enqueue(_ element: T) { if isFull() { resize(newCapacity: capacity * 2) } data[rear] = element rear = (rear + 1) % capacity size += 1 } private func resize(newCapacity: Int) { var newData: [T?] = Array(repeating: nil, count: newCapacity) for i in 0..<size { let index = (front + i) % capacity newData[i] = data[index] } data = newData front = 0 rear = size capacity = newCapacity } func isFull() -> Bool { return size == capacity } }
|
总结
循环数组是一种巧妙的数据结构,通过取模运算实现了数组的逻辑环形连接。它在以下场景中特别有用:
- 需要高效的两端操作:O(1) 时间复杂度的头尾插入删除
- 固定大小的缓冲区:音视频处理、网络通信等
- 队列的实现:循环队列比用链表实现的队列更节省空间
- 资源受限的环境:嵌入式系统、实时系统等
掌握循环数组的原理和实现,对于理解操作系统、网络编程、以及解决算法问题都有很大帮助。
延伸阅读
- Circular buffer - Wikipedia
- Circular Queue | Set 1 (Introduction and Array Implementation) - GeeksforGeeks
- Design Circular Queue - LeetCode
- Design Circular Deque - LeetCode
参考资料
- Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest, Clifford Stein. 《算法导论》(第3版). 机械工业出版社, 2012
- Mark Allen Weiss. 《数据结构与算法分析:C++描述》(第4版). 电子工业出版社, 2014
- 严蔚敏, 吴伟民. 《数据结构》(C语言版). 清华大学出版社, 2011
- Robert Sedgewick, Kevin Wayne. 《算法》(第4版). 人民邮电出版社, 2012